├── .SRCINFO ├── .env ├── .github ├── ISSUE_TEMPLATE │ ├── cn_bug_report.yml │ ├── cn_feature_request.yml │ ├── cn_question.yml │ ├── en_bug_report.yml │ ├── en_feature_request.yml │ └── en_question.yml └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── PKGBUILD ├── README.md ├── block_domain.txt ├── build.bat ├── build.mac.command ├── build.py ├── build.sh ├── build.spec ├── bypass_token_limit.py ├── bypass_version.py ├── check_user_authorized.py ├── config.py ├── cursor_acc_info.py ├── cursor_auth.py ├── cursor_register_manual.py ├── delete_cursor_google.py ├── disable_auto_update.py ├── get_user_token.py ├── images ├── cloudflare_2025-02-12_13-43-21.png ├── fix_2025-01-14_21-30-43.png ├── free_2025-01-14_14-59-15.png ├── locale_2025-01-15_13-40-08.png ├── logo.png ├── new107_2025-01-15_13-53-56.png ├── new_2025-02-27_10-42-44.png ├── new_2025-03-19_00-19-09.png ├── new_2025-03-22_19-53-10.png ├── pass_2025-02-08_21-48-36.png ├── paypal.png ├── pro_2025-01-11_00-50-40.png ├── pro_2025-01-11_00-51-07.png ├── pro_2025-01-11_16-24-03.png ├── pro_2025-01-11_22-33-09.gif ├── pro_2025-01-13_13-49-55.png ├── pro_2025-01-14_14-40-37.png ├── pro_2025-04-05_18-47-56.png ├── product_2025-04-16_10-40-21.png ├── pronew_2025-02-13_15-01-32.png ├── provi-code.jpg └── what_2025-01-13_13-32-54.png ├── locales ├── ar.json ├── bg.json ├── de.json ├── en.json ├── es.json ├── fr.json ├── it.json ├── ja.json ├── nl.json ├── pt.json ├── ru.json ├── tr.json ├── vi.json ├── zh_cn.json └── zh_tw.json ├── logo.py ├── main.py ├── new_signup.py ├── oauth_auth.py ├── quit_cursor.py ├── requirements.txt ├── reset_machine_manual.py ├── restore_machine_id.py ├── scripts ├── install.ps1 ├── install.sh └── reset.ps1 ├── totally_reset_cursor.py └── utils.py /.SRCINFO: -------------------------------------------------------------------------------- 1 | pkgbase = cursor-free-vip-git 2 | pkgdesc = Reset Cursor AI MachineID & Auto Sign Up / In & Bypass Higher Token Limit 3 | pkgver = 1.9.05 4 | pkgrel = 1 5 | url = https://github.com/yeongpin/cursor-free-vip 6 | arch = x86_64 7 | license = MIT 8 | license = Attribution-NonCommercial-NoDerivatives 4.0 International 9 | makedepends = git 10 | makedepends = python 11 | makedepends = pyinstaller 12 | makedepends = uv 13 | depends = python 14 | depends = cursor-bin 15 | provides = cursor-free-vip 16 | source = cursor-free-vip::git+https://github.com/yeongpin/cursor-free-vip.git 17 | source = https://raw.githubusercontent.com/canmi21/openjlc/refs/heads/main/LICENSE 18 | sha256sums = SKIP 19 | sha256sums = SKIP 20 | 21 | pkgname = cursor-free-vip-git 22 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | version=1.11.02 2 | VERSION=1.11.02 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/cn_bug_report.yml: -------------------------------------------------------------------------------- 1 | name: ❌ 错误报告 [中文] 2 | description: 创建一个报告以帮助我们改进 3 | title: '[Bug]: ' 4 | labels: ['bug'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 感谢您花时间填写此错误报告! 10 | 在提交 Issue 前请确保您已经阅读了[Github Issues](https://github.com/yeongpin/cursor-free-vip/issues) 11 | 12 | - type: checkboxes 13 | id: checklist 14 | attributes: 15 | label: 提交前检查 16 | description: | 17 | 请确保您在提交 Issue 前已经完成了以下所有步骤 18 | options: 19 | - label: 我理解 Issue 是用于反馈和解决问题的,而非吐槽评论区,将尽可能提供更多信息帮助问题解决。 20 | required: true 21 | - 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),没有找到类似的问题。 22 | required: true 23 | - label: 我填写了简短且清晰明确的标题,以便开发者在翻阅 Issue 列表时能快速确定大致问题。而不是“一个建议”、“卡住了”等。 24 | required: true 25 | 26 | - type: dropdown 27 | id: platform 28 | attributes: 29 | label: 平台 30 | description: 您正在使用哪个平台? 31 | options: 32 | - Windows x32 33 | - Windows x64 34 | - macOS Intel 35 | - macOS ARM64 36 | - Linux x64 37 | - Linux ARM64 38 | validations: 39 | required: true 40 | 41 | - type: input 42 | id: version 43 | attributes: 44 | label: 版本 45 | description: 您正在运行的 Cursor Free Vip 版本是什么? 46 | placeholder: 例如 v1.0.0 ( 不是 Cursor AI 版本 ) 47 | validations: 48 | required: true 49 | 50 | - type: textarea 51 | id: description 52 | attributes: 53 | label: 错误描述 54 | description: 描述问题时请尽可能详细 55 | placeholder: 告诉我们发生了什么... 56 | validations: 57 | required: true 58 | 59 | - type: textarea 60 | id: logs 61 | attributes: 62 | label: 相关日志输出 63 | description: 请复制并粘贴任何相关的日志输出 64 | render: shell 65 | 66 | - type: textarea 67 | id: additional 68 | attributes: 69 | label: 附加信息 70 | description: 任何能让我们对你所遇到的问题有更多了解的东西 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/cn_feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 💡 功能建议 2 | description: 为这个项目提出新的想法 3 | title: "[功能建议]: " 4 | labels: ["enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 感谢您抽出时间提出新功能建议! 10 | 请确保填写以下所有部分。 11 | - type: textarea 12 | id: feature-description 13 | attributes: 14 | label: 功能描述 15 | description: 清晰简洁地描述您想要实现的功能。 16 | placeholder: "我希望能够..." 17 | validations: 18 | required: true 19 | - type: textarea 20 | id: problem-solution 21 | attributes: 22 | label: 问题和解决方案 23 | description: 描述您试图解决的问题,以及这个功能将如何帮助解决。 24 | placeholder: "目前,当我尝试..." 25 | validations: 26 | required: true 27 | - type: textarea 28 | id: alternatives 29 | attributes: 30 | label: 考虑过的替代方案 31 | description: 描述您考虑过的任何替代解决方案或功能。 32 | placeholder: "我考虑过..." 33 | - type: dropdown 34 | id: priority 35 | attributes: 36 | label: 优先级 37 | description: 这个功能对您来说有多重要? 38 | options: 39 | - 高(对我的工作流程至关重要) 40 | - 中(会很有帮助) 41 | - 低(有更好) 42 | validations: 43 | required: true 44 | - type: dropdown 45 | id: impact 46 | attributes: 47 | label: 影响范围 48 | description: 有多少用户会从这个功能中受益? 49 | options: 50 | - 所有用户 51 | - 大多数用户 52 | - 部分用户 53 | - 仅我个人 54 | validations: 55 | required: true 56 | - type: textarea 57 | id: technical-details 58 | attributes: 59 | label: 技术细节 60 | description: 您想分享的任何技术考虑或实现细节。 61 | placeholder: "这个功能可以通过..." 62 | - type: textarea 63 | id: screenshots 64 | attributes: 65 | label: 截图 66 | description: 如果适用,添加截图以帮助解释您的功能建议。 67 | placeholder: "您可以在此处拖放截图..." 68 | - type: checkboxes 69 | id: terms 70 | attributes: 71 | label: 行为准则 72 | description: 提交此问题即表示您同意遵守本项目的行为准则 73 | options: 74 | - label: 我同意遵守本项目的行为准则 75 | required: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/cn_question.yml: -------------------------------------------------------------------------------- 1 | name: ❓ 讨论 & 提问 (中文) 2 | description: 寻求帮助、讨论问题、提出疑问等... 3 | title: '[讨论]: ' 4 | labels: ['question'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | 感谢您的提问!请尽可能详细地描述您的问题,这样我们才能更好地帮助您。 10 | 11 | - type: checkboxes 12 | id: checklist 13 | attributes: 14 | label: Issue 检查清单 15 | description: | 16 | 在提交 Issue 前请确保您已经完成了以下所有步骤 17 | options: 18 | - label: 我理解 Issue 是用于反馈和解决问题的,而非吐槽评论区,将尽可能提供更多信息帮助问题解决。 19 | required: true 20 | - label: 我确认自己需要的是提出问题并且讨论问题,而不是 Bug 反馈或需求建议。 21 | required: true 22 | - 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),没有找到类似的问题。 23 | required: true 24 | 25 | - type: dropdown 26 | id: platform 27 | attributes: 28 | label: 平台 29 | description: 您正在使用哪个平台? 30 | options: 31 | - Windows x32 32 | - Windows x64 33 | - macOS Intel 34 | - macOS ARM64 35 | - Linux x64 36 | - Linux ARM64 37 | validations: 38 | required: true 39 | 40 | - type: input 41 | id: version 42 | attributes: 43 | label: 版本 44 | description: 您正在运行的 Cursor Free Vip 版本是什么? 45 | placeholder: 例如 v1.0.0 46 | validations: 47 | required: true 48 | 49 | - type: textarea 50 | id: question 51 | attributes: 52 | label: 您的问题 53 | description: 请详细描述您的问题 54 | placeholder: 请尽可能清楚地说明您的问题... 55 | validations: 56 | required: true 57 | 58 | - type: textarea 59 | id: additional 60 | attributes: 61 | label: 补充信息 62 | description: 任何其他相关的信息、截图或代码示例 63 | render: shell 64 | 65 | - type: dropdown 66 | id: priority 67 | attributes: 68 | label: 优先级 69 | description: 这个问题对您来说有多紧急? 70 | options: 71 | - 低 (有空再看) 72 | - 中 (希望尽快得到答复) 73 | - 高 (阻碍工作进行) 74 | validations: 75 | required: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/en_bug_report.yml: -------------------------------------------------------------------------------- 1 | name: ❌ Bug Report [English] 2 | description: Create a report to help us improve 3 | title: '[Bug]: ' 4 | labels: ['bug'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thank you for taking the time to fill out this bug report! 10 | Before submitting this issue, please ensure that you have read the [github issues](https://github.com/yeongpin/cursor-free-vip/issues) 11 | 12 | - type: checkboxes 13 | id: checklist 14 | attributes: 15 | label: Commit before submitting 16 | description: | 17 | Please ensure that you have completed all of the following steps before submitting an issue 18 | options: 19 | - 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. 20 | required: true 21 | - 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. 22 | required: true 23 | - 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. 24 | required: true 25 | 26 | - type: dropdown 27 | id: platform 28 | attributes: 29 | label: Platform 30 | description: Which platform are you using? 31 | options: 32 | - Windows x32 33 | - Windows x64 34 | - macOS Intel 35 | - macOS ARM64 36 | - Linux x64 37 | - Linux ARM64 38 | validations: 39 | required: true 40 | 41 | - type: input 42 | id: version 43 | attributes: 44 | label: Version 45 | description: What version of Cursor Free Vip are you running? 46 | placeholder: For example v1.0.0 ( Not Cursor AI Version ) 47 | validations: 48 | required: true 49 | 50 | - type: textarea 51 | id: description 52 | attributes: 53 | label: Description 54 | description: Please describe the problem as detailed as possible 55 | placeholder: Tell us what happened... 56 | validations: 57 | required: true 58 | 59 | - type: textarea 60 | id: logs 61 | attributes: 62 | label: Related log output 63 | description: Please copy and paste any related log output 64 | render: shell 65 | 66 | - type: textarea 67 | id: additional 68 | attributes: 69 | label: Additional information 70 | description: Anything that might help us understand the problem better -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/en_feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 💡 Feature Request 2 | description: Suggest an idea for this project 3 | title: "[Feature]: " 4 | labels: ["enhancement"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to suggest a new feature! 10 | Please make sure to fill out all the sections below. 11 | - type: textarea 12 | id: feature-description 13 | attributes: 14 | label: Feature Description 15 | description: A clear and concise description of what you want to happen. 16 | placeholder: "I would like to..." 17 | validations: 18 | required: true 19 | - type: textarea 20 | id: problem-solution 21 | attributes: 22 | label: Problem & Solution 23 | description: Describe the problem you're trying to solve and how this feature would help. 24 | placeholder: "Currently, when I try to..." 25 | validations: 26 | required: true 27 | - type: textarea 28 | id: alternatives 29 | attributes: 30 | label: Alternatives Considered 31 | description: Describe any alternative solutions or features you've considered. 32 | placeholder: "I've considered..." 33 | - type: dropdown 34 | id: priority 35 | attributes: 36 | label: Priority 37 | description: How important is this feature to you? 38 | options: 39 | - High (Critical for my workflow) 40 | - Medium (Would be very helpful) 41 | - Low (Nice to have) 42 | validations: 43 | required: true 44 | - type: dropdown 45 | id: impact 46 | attributes: 47 | label: Impact 48 | description: How many users would benefit from this feature? 49 | options: 50 | - All users 51 | - Most users 52 | - Some users 53 | - Just me 54 | validations: 55 | required: true 56 | - type: textarea 57 | id: technical-details 58 | attributes: 59 | label: Technical Details 60 | description: Any technical considerations or implementation details you'd like to share. 61 | placeholder: "The feature could be implemented by..." 62 | - type: textarea 63 | id: screenshots 64 | attributes: 65 | label: Screenshots 66 | description: If applicable, add screenshots to help explain your feature. 67 | placeholder: "You can drag and drop screenshots here..." 68 | - type: checkboxes 69 | id: terms 70 | attributes: 71 | label: Code of Conduct 72 | description: By submitting this issue, you agree to follow this project's Code of Conduct 73 | options: 74 | - label: I agree to follow this project's Code of Conduct 75 | required: true -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/en_question.yml: -------------------------------------------------------------------------------- 1 | name: ❓ Discussion & Question [English] 2 | description: Seeking help, discussing problems, asking questions, etc. 3 | title: '[Discussion]: ' 4 | labels: ['question'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thank you for your question! Please describe your problem as detailed as possible so we can help you better. 10 | 11 | - type: checkboxes 12 | id: checklist 13 | attributes: 14 | label: Issue Checklist 15 | description: | 16 | Please ensure that you have completed all of the following steps before submitting an issue 17 | options: 18 | - 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. 19 | required: true 20 | - label: I confirm that I need to raise questions and discuss problems, not Bug feedback or demand suggestions. 21 | required: true 22 | - 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. 23 | required: true 24 | 25 | - type: dropdown 26 | id: platform 27 | attributes: 28 | label: Platform 29 | description: Which platform are you using? 30 | options: 31 | - Windows x32 32 | - Windows x64 33 | - macOS Intel 34 | - macOS ARM64 35 | - Linux x64 36 | - Linux ARM64 37 | validations: 38 | required: true 39 | 40 | - type: input 41 | id: version 42 | attributes: 43 | label: Version 44 | description: What version of Cursor Free Vip are you running? 45 | placeholder: For example v1.0.0 46 | validations: 47 | required: true 48 | 49 | - type: textarea 50 | id: question 51 | attributes: 52 | label: Your question 53 | description: Please describe your problem as detailed as possible 54 | placeholder: Please explain your question as clearly as possible... 55 | validations: 56 | required: true 57 | 58 | - type: textarea 59 | id: additional 60 | attributes: 61 | label: Additional information 62 | description: Any other related information, screenshots, or code examples 63 | render: shell 64 | 65 | - type: dropdown 66 | id: priority 67 | attributes: 68 | label: Priority 69 | description: How urgent is this issue for you? 70 | options: 71 | - Low (I'll look at it when I have time) 72 | - Medium (I hope to get an answer soon) 73 | - High (It blocks my work) 74 | validations: 75 | required: true -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build Executables 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | use_env_version: 7 | description: 'Use version from .env file (yes/no)' 8 | required: true 9 | default: 'yes' 10 | version: 11 | description: 'Version number (only used if not using .env version)' 12 | required: false 13 | default: '' 14 | 15 | permissions: 16 | contents: write 17 | actions: write 18 | packages: write 19 | 20 | jobs: 21 | determine-version: 22 | runs-on: ubuntu-latest 23 | outputs: 24 | version: ${{ steps.set-version.outputs.version }} 25 | steps: 26 | - uses: actions/checkout@v2 27 | 28 | - name: Get version from .env file 29 | id: env-version 30 | if: ${{ github.event.inputs.use_env_version == 'yes' }} 31 | run: | 32 | VERSION=$(grep "^version=" .env | cut -d'=' -f2) 33 | echo "ENV_VERSION=$VERSION" >> $GITHUB_ENV 34 | echo "version=$VERSION" >> $GITHUB_OUTPUT 35 | echo "Using version from .env file: $VERSION" 36 | 37 | - name: Use manual version 38 | id: manual-version 39 | if: ${{ github.event.inputs.use_env_version != 'yes' }} 40 | run: | 41 | echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT 42 | echo "Using manually entered version: ${{ github.event.inputs.version }}" 43 | 44 | - name: Set final version 45 | id: set-version 46 | run: | 47 | if [ "${{ github.event.inputs.use_env_version }}" == "yes" ]; then 48 | echo "version=${{ env.ENV_VERSION }}" >> $GITHUB_OUTPUT 49 | else 50 | echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT 51 | fi 52 | 53 | create-tag: 54 | needs: determine-version 55 | runs-on: ubuntu-latest 56 | steps: 57 | - uses: actions/checkout@v2 58 | with: 59 | fetch-depth: 0 # 获取所有标签 60 | 61 | - name: Check if tag exists 62 | id: check_tag 63 | run: | 64 | if git ls-remote --tags origin | grep -q "refs/tags/v${{ needs.determine-version.outputs.version }}"; then 65 | echo "Tag v${{ needs.determine-version.outputs.version }} already exists, will use existing tag" 66 | echo "tag_exists=true" >> $GITHUB_OUTPUT 67 | else 68 | echo "Tag v${{ needs.determine-version.outputs.version }} does not exist, will create new tag" 69 | echo "tag_exists=false" >> $GITHUB_OUTPUT 70 | fi 71 | 72 | - name: Create Tag if not exists 73 | if: steps.check_tag.outputs.tag_exists == 'false' 74 | run: | 75 | git tag "v${{ needs.determine-version.outputs.version }}" 76 | git push origin "v${{ needs.determine-version.outputs.version }}" 77 | 78 | build-windows: 79 | needs: [determine-version, create-tag] 80 | runs-on: windows-latest 81 | 82 | steps: 83 | - uses: actions/checkout@v2 84 | 85 | - name: Set up Python 86 | uses: actions/setup-python@v2 87 | with: 88 | python-version: '3.x' 89 | 90 | - name: Set version 91 | shell: bash 92 | run: echo "VERSION=${{ needs.determine-version.outputs.version }}" >> $GITHUB_ENV 93 | 94 | - name: Install dependencies 95 | run: | 96 | python -m pip install --upgrade pip 97 | pip install pyinstaller 98 | pip install -r requirements.txt 99 | 100 | - name: Build EXE 101 | run: | 102 | pyinstaller build.spec 103 | 104 | - name: Upload Windows artifact 105 | uses: actions/upload-artifact@v4 106 | with: 107 | name: CursorFreeVIP_${{ env.VERSION }}_windows.exe 108 | path: dist/CursorFreeVIP_${{ env.VERSION }}_windows.exe 109 | 110 | build-macos-arm64: 111 | needs: [determine-version, create-tag] 112 | runs-on: macos-latest 113 | 114 | steps: 115 | - uses: actions/checkout@v2 116 | 117 | - name: Set up Python 118 | uses: actions/setup-python@v2 119 | with: 120 | python-version: '3.x' 121 | 122 | - name: Set version 123 | shell: bash 124 | run: echo "VERSION=${{ needs.determine-version.outputs.version }}" >> $GITHUB_ENV 125 | 126 | - name: Install dependencies 127 | run: | 128 | python -m pip install --upgrade pip 129 | pip install pyinstaller 130 | pip install -r requirements.txt 131 | 132 | - name: Build MacOS ARM executable 133 | run: | 134 | pyinstaller build.spec 135 | mv "dist/CursorFreeVIP_${{ env.VERSION }}_mac" "dist/CursorFreeVIP_${{ env.VERSION }}_mac_arm64" 136 | 137 | - name: Upload MacOS ARM artifact 138 | uses: actions/upload-artifact@v4 139 | with: 140 | name: CursorFreeVIP_${{ env.VERSION }}_mac_arm64 141 | path: dist/CursorFreeVIP_${{ env.VERSION }}_mac_arm64 142 | 143 | build-linux-x64: 144 | needs: [determine-version, create-tag] 145 | runs-on: ubuntu-22.04 146 | 147 | steps: 148 | - uses: actions/checkout@v2 149 | 150 | - name: Set up Python 151 | uses: actions/setup-python@v2 152 | with: 153 | python-version: '3.x' 154 | 155 | - name: Set version 156 | shell: bash 157 | run: echo "VERSION=${{ needs.determine-version.outputs.version }}" >> $GITHUB_ENV 158 | 159 | - name: Install dependencies 160 | run: | 161 | python -m pip install --upgrade pip 162 | pip install pyinstaller 163 | pip install -r requirements.txt 164 | 165 | - name: Build Linux x64 executable 166 | env: 167 | VERSION: ${{ env.VERSION }} 168 | run: | 169 | pyinstaller build.spec 170 | mv "dist/CursorFreeVIP_${{ env.VERSION }}_linux" "dist/CursorFreeVIP_${{ env.VERSION }}_linux_x64" 171 | echo "Contents of dist directory:" 172 | ls -la dist/ 173 | 174 | - name: Upload Linux x64 artifact 175 | uses: actions/upload-artifact@v4 176 | with: 177 | name: CursorFreeVIP_${{ env.VERSION }}_linux_x64 178 | path: dist/CursorFreeVIP_${{ env.VERSION }}_linux_x64 179 | 180 | build-linux-arm64: 181 | needs: [determine-version, create-tag] 182 | runs-on: ubuntu-latest 183 | 184 | steps: 185 | - uses: actions/checkout@v2 186 | 187 | - name: Set up QEMU 188 | uses: docker/setup-qemu-action@v2 189 | with: 190 | platforms: arm64 191 | 192 | - name: Set version 193 | shell: bash 194 | run: echo "VERSION=${{ needs.determine-version.outputs.version }}" >> $GITHUB_ENV 195 | 196 | - name: Build in ARM64 Docker container 197 | run: | 198 | docker run --rm --platform linux/arm64 -v ${{ github.workspace }}:/app -w /app arm64v8/python:3.10-slim bash -c " 199 | apt-get update && apt-get install -y build-essential 200 | pip install --upgrade pip 201 | pip install pyinstaller 202 | pip install -r requirements.txt 203 | python -m PyInstaller build.spec 204 | mv /app/dist/CursorFreeVIP_${{ env.VERSION }}_linux /app/dist/CursorFreeVIP_${{ env.VERSION }}_linux_arm64 205 | " 206 | echo "Contents of dist directory:" 207 | ls -la dist/ 208 | 209 | - name: Upload Linux ARM64 artifact 210 | uses: actions/upload-artifact@v4 211 | with: 212 | name: CursorFreeVIP_${{ env.VERSION }}_linux_arm64 213 | path: dist/CursorFreeVIP_${{ env.VERSION }}_linux_arm64 214 | 215 | build-macos-intel: 216 | needs: [determine-version, create-tag] 217 | runs-on: macos-latest 218 | 219 | steps: 220 | - uses: actions/checkout@v2 221 | 222 | - name: Set up Python 223 | uses: actions/setup-python@v2 224 | with: 225 | python-version: '3.x' 226 | 227 | - name: Set version 228 | shell: bash 229 | run: echo "VERSION=${{ needs.determine-version.outputs.version }}" >> $GITHUB_ENV 230 | 231 | - name: Install dependencies 232 | run: | 233 | arch -x86_64 pip3 install --upgrade pip 234 | arch -x86_64 pip3 install pyinstaller 235 | arch -x86_64 pip3 install -r requirements.txt 236 | 237 | - name: Build MacOS Intel executable 238 | env: 239 | TARGET_ARCH: 'x86_64' 240 | VERSION: ${{ env.VERSION }} 241 | run: | 242 | arch -x86_64 python3 -m PyInstaller build.spec 243 | mv "dist/CursorFreeVIP_${{ env.VERSION }}_mac" "dist/CursorFreeVIP_${{ env.VERSION }}_mac_intel" 244 | 245 | - name: Upload MacOS Intel artifact 246 | uses: actions/upload-artifact@v4 247 | with: 248 | name: CursorFreeVIP_${{ env.VERSION }}_mac_intel 249 | path: dist/CursorFreeVIP_${{ env.VERSION }}_mac_intel 250 | 251 | create-release: 252 | needs: [determine-version, build-windows, build-macos-arm64, build-linux-x64, build-linux-arm64, build-macos-intel] 253 | runs-on: ubuntu-22.04 254 | 255 | steps: 256 | - name: Checkout code 257 | uses: actions/checkout@v2 258 | 259 | - name: Get version 260 | shell: bash 261 | run: echo "VERSION=${{ needs.determine-version.outputs.version }}" >> $GITHUB_ENV 262 | 263 | - name: Download all artifacts 264 | uses: actions/download-artifact@v4 265 | with: 266 | path: artifacts 267 | 268 | - name: Calculate SHA256 checksums 269 | run: | 270 | mkdir -p checksums 271 | for file in artifacts/CursorFreeVIP_${{ env.VERSION }}_windows.exe/CursorFreeVIP_${{ env.VERSION }}_windows.exe \ 272 | artifacts/CursorFreeVIP_${{ env.VERSION }}_mac_arm64/CursorFreeVIP_${{ env.VERSION }}_mac_arm64 \ 273 | artifacts/CursorFreeVIP_${{ env.VERSION }}_linux_x64/CursorFreeVIP_${{ env.VERSION }}_linux_x64 \ 274 | artifacts/CursorFreeVIP_${{ env.VERSION }}_linux_arm64/CursorFreeVIP_${{ env.VERSION }}_linux_arm64 \ 275 | artifacts/CursorFreeVIP_${{ env.VERSION }}_mac_intel/CursorFreeVIP_${{ env.VERSION }}_mac_intel 276 | do 277 | if [ -f "$file" ]; then 278 | filename=$(basename $file) 279 | sha256sum "$file" | cut -d ' ' -f 1 > checksums/${filename}.sha256 280 | echo "${filename}: $(cat checksums/${filename}.sha256)" >> checksums/all_checksums.txt 281 | else 282 | echo "Warning: File $file not found" 283 | fi 284 | done 285 | cat checksums/all_checksums.txt 286 | 287 | - name: Extract release notes from CHANGELOG 288 | run: | 289 | version_pattern="## v${{ env.VERSION }}" 290 | next_version_pattern="## v" 291 | 292 | # Find the start line number of the current version 293 | start_line=$(grep -n "$version_pattern" CHANGELOG.md | head -1 | cut -d: -f1) 294 | 295 | if [ -z "$start_line" ]; then 296 | echo "Error: Version ${{ env.VERSION }} not found in CHANGELOG.md" 297 | exit 1 298 | fi 299 | 300 | # Find the line number of the next version 301 | next_version_line=$(tail -n +$((start_line + 1)) CHANGELOG.md | grep -n "$next_version_pattern" | head -1 | cut -d: -f1) 302 | 303 | if [ -z "$next_version_line" ]; then 304 | # If there's no next version, get to the end of the file 305 | changelog_content=$(tail -n +$start_line CHANGELOG.md) 306 | else 307 | # Extract content between current version and next version 308 | end_line=$((start_line + next_version_line - 1)) 309 | changelog_content=$(sed -n "${start_line},${end_line}p" CHANGELOG.md) 310 | fi 311 | 312 | # Create release notes file 313 | { 314 | echo "$changelog_content" 315 | echo "" 316 | echo "## SHA256 Checksums" 317 | cat checksums/all_checksums.txt 318 | } > release_notes.md 319 | 320 | # Display release notes for debugging 321 | cat release_notes.md 322 | 323 | - name: Create Release 324 | uses: softprops/action-gh-release@v2 325 | with: 326 | tag_name: v${{ env.VERSION }} 327 | body_path: release_notes.md 328 | files: | 329 | artifacts/CursorFreeVIP_${{ env.VERSION }}_windows.exe/CursorFreeVIP_${{ env.VERSION }}_windows.exe 330 | artifacts/CursorFreeVIP_${{ env.VERSION }}_mac_arm64/CursorFreeVIP_${{ env.VERSION }}_mac_arm64 331 | artifacts/CursorFreeVIP_${{ env.VERSION }}_linux_x64/CursorFreeVIP_${{ env.VERSION }}_linux_x64 332 | artifacts/CursorFreeVIP_${{ env.VERSION }}_linux_arm64/CursorFreeVIP_${{ env.VERSION }}_linux_arm64 333 | artifacts/CursorFreeVIP_${{ env.VERSION }}_mac_intel/CursorFreeVIP_${{ env.VERSION }}_mac_intel 334 | draft: false 335 | prerelease: false 336 | env: 337 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | server/ 3 | venv/ 4 | check_license.py 5 | cursor_modifier.py 6 | reset_machine.py 7 | Run_Venv.bat 8 | token_monitor.py 9 | get_mac.py 10 | .gitignore 11 | build.bat 12 | build.mac.command 13 | build.py 14 | build.sh 15 | ENV/ 16 | test.py 17 | new_tempemail_smail.py 18 | new_tempemail_api.py 19 | 20 | install.bat 21 | run.bat 22 | 23 | temp_account_info.txt 24 | 25 | .env copy 26 | 27 | # PyInstaller 28 | build/ 29 | dist/ 30 | *.spec2 31 | 32 | credentials.txt 33 | cursor_accounts.txt 34 | recaptcha.py 35 | install_requirements.bat 36 | 37 | # IDE 38 | .idea/ 39 | .vscode/ 40 | *.swp 41 | *.swo 42 | 43 | # OS 44 | .DS_Store 45 | Thumbs.db 46 | 47 | # Project specific 48 | *.log 49 | *.db 50 | *.sqlite3 51 | 52 | # Mac 53 | run_venv.mac.command -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # Attribution-NonCommercial-NoDerivatives 4.0 International 2 | 3 | > *Creative Commons Corporation (“Creative Commons”) is not a law firm and does not provide legal services or legal advice. Distribution of Creative Commons public licenses does not create a lawyer-client or other relationship. Creative Commons makes its licenses and related information available on an “as-is” basis. Creative Commons gives no warranties regarding its licenses, any material licensed under their terms and conditions, or any related information. Creative Commons disclaims all liability for damages resulting from their use to the fullest extent possible.* 4 | > 5 | > ### Using Creative Commons Public Licenses 6 | > 7 | > Creative Commons public licenses provide a standard set of terms and conditions that creators and other rights holders may use to share original works of authorship and other material subject to copyright and certain other rights specified in the public license below. The following considerations are for informational purposes only, are not exhaustive, and do not form part of our licenses. 8 | > 9 | > * __Considerations for licensors:__ Our public licenses are intended for use by those authorized to give the public permission to use material in ways otherwise restricted by copyright and certain other rights. Our licenses are irrevocable. Licensors should read and understand the terms and conditions of the license they choose before applying it. Licensors should also secure all rights necessary before applying our licenses so that the public can reuse the material as expected. Licensors should clearly mark any material not subject to the license. This includes other CC-licensed material, or material used under an exception or limitation to copyright. [More considerations for licensors](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensors). 10 | > 11 | > * __Considerations for the public:__ By using one of our public licenses, a licensor grants the public permission to use the licensed material under specified terms and conditions. If the licensor’s permission is not necessary for any reason–for example, because of any applicable exception or limitation to copyright–then that use is not regulated by the license. Our licenses grant only permissions under copyright and certain other rights that a licensor has authority to grant. Use of the licensed material may still be restricted for other reasons, including because others have copyright or other rights in the material. A licensor may make special requests, such as asking that all changes be marked or described. Although not required by our licenses, you are encouraged to respect those requests where reasonable. [More considerations for the public](http://wiki.creativecommons.org/Considerations_for_licensors_and_licensees#Considerations_for_licensees). 12 | 13 | ## Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Public License 14 | 15 | By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions. 16 | 17 | ### Section 1 – Definitions. 18 | 19 | a. __Adapted Material__ means material subject to Copyright and Similar Rights that is derived from or based upon the Licensed Material and in which the Licensed Material is translated, altered, arranged, transformed, or otherwise modified in a manner requiring permission under the Copyright and Similar Rights held by the Licensor. For purposes of this Public License, where the Licensed Material is a musical work, performance, or sound recording, Adapted Material is always produced where the Licensed Material is synched in timed relation with a moving image. 20 | 21 | b. __Copyright and Similar Rights__ means copyright and/or similar rights closely related to copyright including, without limitation, performance, broadcast, sound recording, and Sui Generis Database Rights, without regard to how the rights are labeled or categorized. For purposes of this Public License, the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. 22 | 23 | e. __Effective Technological Measures__ means those measures that, in the absence of proper authority, may not be circumvented under laws fulfilling obligations under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, and/or similar international agreements. 24 | 25 | f. __Exceptions and Limitations__ means fair use, fair dealing, and/or any other exception or limitation to Copyright and Similar Rights that applies to Your use of the Licensed Material. 26 | 27 | h. __Licensed Material__ means the artistic or literary work, database, or other material to which the Licensor applied this Public License. 28 | 29 | i. __Licensed Rights__ means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license. 30 | 31 | h. __Licensor__ means the individual(s) or entity(ies) granting rights under this Public License. 32 | 33 | i. __NonCommercial__ means not primarily intended for or directed towards commercial advantage or monetary compensation. For purposes of this Public License, the exchange of the Licensed Material for other material subject to Copyright and Similar Rights by digital file-sharing or similar means is NonCommercial provided there is no payment of monetary compensation in connection with the exchange. 34 | 35 | j. __Share__ means to provide material to the public by any means or process that requires permission under the Licensed Rights, such as reproduction, public display, public performance, distribution, dissemination, communication, or importation, and to make material available to the public including in ways that members of the public may access the material from a place and at a time individually chosen by them. 36 | 37 | k. __Sui Generis Database Rights__ means rights other than copyright resulting from Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, as amended and/or succeeded, as well as other essentially equivalent rights anywhere in the world. 38 | 39 | l. __You__ means the individual or entity exercising the Licensed Rights under this Public License. Your has a corresponding meaning. 40 | 41 | ### Section 2 – Scope. 42 | 43 | a. ___License grant.___ 44 | 45 | 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to: 46 | 47 | A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and 48 | 49 | B. produce and reproduce, but not Share, Adapted Material for NonCommercial purposes only. 50 | 51 | 2. __Exceptions and Limitations.__ For the avoidance of doubt, where Exceptions and Limitations apply to Your use, this Public License does not apply, and You do not need to comply with its terms and conditions. 52 | 53 | 3. __Term.__ The term of this Public License is specified in Section 6(a). 54 | 55 | 4. __Media and formats; technical modifications allowed.__ The Licensor authorizes You to exercise the Licensed Rights in all media and formats whether now known or hereafter created, and to make technical modifications necessary to do so. The Licensor waives and/or agrees not to assert any right or authority to forbid You from making technical modifications necessary to exercise the Licensed Rights, including technical modifications necessary to circumvent Effective Technological Measures. For purposes of this Public License, simply making modifications authorized by this Section 2(a)(4) never produces Adapted Material. 56 | 57 | 5. __Downstream recipients.__ 58 | 59 | A. __Offer from the Licensor – Licensed Material.__ Every recipient of the Licensed Material automatically receives an offer from the Licensor to exercise the Licensed Rights under the terms and conditions of this Public License. 60 | 61 | B. __No downstream restrictions.__ You may not offer or impose any additional or different terms or conditions on, or apply any Effective Technological Measures to, the Licensed Material if doing so restricts exercise of the Licensed Rights by any recipient of the Licensed Material. 62 | 63 | 6. __No endorsement.__ Nothing in this Public License constitutes or may be construed as permission to assert or imply that You are, or that Your use of the Licensed Material is, connected with, or sponsored, endorsed, or granted official status by, the Licensor or others designated to receive attribution as provided in Section 3(a)(1)(A)(i). 64 | 65 | b. ___Other rights.___ 66 | 67 | 1. Moral rights, such as the right of integrity, are not licensed under this Public License, nor are publicity, privacy, and/or other similar personality rights; however, to the extent possible, the Licensor waives and/or agrees not to assert any such rights held by the Licensor to the limited extent necessary to allow You to exercise the Licensed Rights, but not otherwise. 68 | 69 | 2. Patent and trademark rights are not licensed under this Public License. 70 | 71 | 3. To the extent possible, the Licensor waives any right to collect royalties from You for the exercise of the Licensed Rights, whether directly or through a collecting society under any voluntary or waivable statutory or compulsory licensing scheme. In all other cases the Licensor expressly reserves any right to collect such royalties, including when the Licensed Material is used other than for NonCommercial purposes. 72 | 73 | ### Section 3 – License Conditions. 74 | 75 | Your exercise of the Licensed Rights is expressly made subject to the following conditions. 76 | 77 | a. ___Attribution.___ 78 | 79 | 1. If You Share the Licensed Material, You must: 80 | 81 | A. retain the following if it is supplied by the Licensor with the Licensed Material: 82 | 83 | i. identification of the creator(s) of the Licensed Material and any others designated to receive attribution, in any reasonable manner requested by the Licensor (including by pseudonym if designated); 84 | 85 | ii. a copyright notice; 86 | 87 | iii. a notice that refers to this Public License; 88 | 89 | iv. a notice that refers to the disclaimer of warranties; 90 | 91 | v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 92 | 93 | B. indicate if You modified the Licensed Material and retain an indication of any previous modifications; and 94 | 95 | C. indicate the Licensed Material is licensed under this Public License, and include the text of, or the URI or hyperlink to, this Public License. 96 | 97 | For the avoidance of doubt, You do not have permission under this Public License to Share Adapted Material. 98 | 99 | 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner based on the medium, means, and context in which You Share the Licensed Material. For example, it may be reasonable to satisfy the conditions by providing a URI or hyperlink to a resource that includes the required information. 100 | 101 | 3. If requested by the Licensor, You must remove any of the information required by Section 3(a)(1)(A) to the extent reasonably practicable. 102 | 103 | ### Section 4 – Sui Generis Database Rights. 104 | 105 | Where the Licensed Rights include Sui Generis Database Rights that apply to Your use of the Licensed Material: 106 | 107 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, reuse, reproduce, and Share all or a substantial portion of the contents of the database for NonCommercial purposes only and provided You do not Share Adapted Material; 108 | 109 | b. if You include all or a substantial portion of the database contents in a database in which You have Sui Generis Database Rights, then the database in which You have Sui Generis Database Rights (but not its individual contents) is Adapted Material; and 110 | 111 | c. You must comply with the conditions in Section 3(a) if You Share all or a substantial portion of the contents of the database. 112 | 113 | For the avoidance of doubt, this Section 4 supplements and does not replace Your obligations under this Public License where the Licensed Rights include other Copyright and Similar Rights. 114 | 115 | ### Section 5 – Disclaimer of Warranties and Limitation of Liability. 116 | 117 | a. __Unless otherwise separately undertaken by the Licensor, to the extent possible, the Licensor offers the Licensed Material as-is and as-available, and makes no representations or warranties of any kind concerning the Licensed Material, whether express, implied, statutory, or other. This includes, without limitation, warranties of title, merchantability, fitness for a particular purpose, non-infringement, absence of latent or other defects, accuracy, or the presence or absence of errors, whether or not known or discoverable. Where disclaimers of warranties are not allowed in full or in part, this disclaimer may not apply to You.__ 118 | 119 | b. __To the extent possible, in no event will the Licensor be liable to You on any legal theory (including, without limitation, negligence) or otherwise for any direct, special, indirect, incidental, consequential, punitive, exemplary, or other losses, costs, expenses, or damages arising out of this Public License or use of the Licensed Material, even if the Licensor has been advised of the possibility of such losses, costs, expenses, or damages. Where a limitation of liability is not allowed in full or in part, this limitation may not apply to You.__ 120 | 121 | c. The disclaimer of warranties and limitation of liability provided above shall be interpreted in a manner that, to the extent possible, most closely approximates an absolute disclaimer and waiver of all liability. 122 | 123 | ### Section 6 – Term and Termination. 124 | 125 | a. This Public License applies for the term of the Copyright and Similar Rights licensed here. However, if You fail to comply with this Public License, then Your rights under this Public License terminate automatically. 126 | 127 | b. Where Your right to use the Licensed Material has terminated under Section 6(a), it reinstates: 128 | 129 | 1. automatically as of the date the violation is cured, provided it is cured within 30 days of Your discovery of the violation; or 130 | 131 | 2. upon express reinstatement by the Licensor. 132 | 133 | For the avoidance of doubt, this Section 6(b) does not affect any right the Licensor may have to seek remedies for Your violations of this Public License. 134 | 135 | c. For the avoidance of doubt, the Licensor may also offer the Licensed Material under separate terms or conditions or stop distributing the Licensed Material at any time; however, doing so will not terminate this Public License. 136 | 137 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 138 | 139 | ### Section 7 – Other Terms and Conditions. 140 | 141 | a. The Licensor shall not be bound by any additional or different terms or conditions communicated by You unless expressly agreed. 142 | 143 | b. Any arrangements, understandings, or agreements regarding the Licensed Material not stated herein are separate from and independent of the terms and conditions of this Public License. 144 | 145 | ### Section 8 – Interpretation. 146 | 147 | a. For the avoidance of doubt, this Public License does not, and shall not be interpreted to, reduce, limit, restrict, or impose conditions on any use of the Licensed Material that could lawfully be made without permission under this Public License. 148 | 149 | b. To the extent possible, if any provision of this Public License is deemed unenforceable, it shall be automatically reformed to the minimum extent necessary to make it enforceable. If the provision cannot be reformed, it shall be severed from this Public License without affecting the enforceability of the remaining terms and conditions. 150 | 151 | c. No term or condition of this Public License will be waived and no failure to comply consented to unless expressly agreed to by the Licensor. 152 | 153 | d. Nothing in this Public License constitutes or may be interpreted as a limitation upon, or waiver of, any privileges and immunities that apply to the Licensor or You, including from the legal processes of any jurisdiction or authority. 154 | 155 | > Creative Commons is not a party to its public licenses. Notwithstanding, Creative Commons may elect to apply one of its public licenses to material it publishes and in those instances will be considered the “Licensor.” Except for the limited purpose of indicating that material is shared under a Creative Commons public license or as otherwise permitted by the Creative Commons policies published at [creativecommons.org/policies](http://creativecommons.org/policies), Creative Commons does not authorize the use of the trademark “Creative Commons” or any other trademark or logo of Creative Commons without its prior written consent including, without limitation, in connection with any unauthorized modifications to any of its public licenses or any other arrangements, understandings, or agreements concerning use of licensed material. For the avoidance of doubt, this paragraph does not form part of the public licenses. 156 | > 157 | > Creative Commons may be contacted at [creativecommons.org](http://creativecommons.org). 158 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Canmi21 <9997200@qq.com> 2 | # Contributor: Canmi (Canmi21) 3 | 4 | pkgname=cursor-free-vip-git 5 | pkgver=1.9.05 6 | pkgrel=1 7 | pkgdesc="Reset Cursor AI MachineID & Auto Sign Up / In & Bypass Higher Token Limit" 8 | arch=('x86_64') 9 | url="https://github.com/yeongpin/cursor-free-vip" 10 | license=('MIT' 'Attribution-NonCommercial-NoDerivatives 4.0 International') 11 | depends=('python' 'cursor-bin') 12 | makedepends=('git' 'python' 'pyinstaller' 'uv') 13 | provides=('cursor-free-vip') 14 | source=("cursor-free-vip::git+https://github.com/yeongpin/cursor-free-vip.git" "https://raw.githubusercontent.com/canmi21/openjlc/refs/heads/main/LICENSE") 15 | sha256sums=('SKIP' 'SKIP') 16 | 17 | pkgver() { 18 | cd "$srcdir/cursor-free-vip" 19 | git describe --tags --always | sed 's/^v//;s/-/./g' 20 | } 21 | 22 | build() { 23 | cd "$srcdir/cursor-free-vip" 24 | uv venv .venv 25 | source .venv/bin/activate 26 | uv pip install -r requirements.txt 27 | pyinstaller --clean --noconfirm --onefile main.py --name cursor-free-vip 28 | } 29 | 30 | package() { 31 | install -Dm644 "$srcdir/LICENSE" "$pkgdir/usr/share/licenses/$pkgname/mit_license" 32 | install -Dm644 "$srcdir/cursor-free-vip/LICENSE.md" "$pkgdir/usr/share/licenses/$pkgname/attribution_non_commercial_no_derivatives_license" 33 | install -Dm755 "$srcdir/cursor-free-vip/dist/cursor-free-vip" "$pkgdir/usr/bin/cursor-free-vip" 34 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ➤ Cursor Free VIP 2 | 3 |
4 |

5 | Cursor Pro Logo 6 |

7 | 8 |

9 | 10 | [![Release](https://img.shields.io/endpoint?url=https://www.pinnumber.rr.nu/badges/release/yeongpin/cursor-free-vip)](https://github.com/yeongpin/cursor-free-vip/releases/latest) 11 | [![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/) 12 | [![Stars](https://img.shields.io/endpoint?url=https://www.pinnumber.rr.nu/badges/stars/yeongpin/cursor-free-vip)](https://github.com/yeongpin/cursor-free-vip/stargazers) 13 | [![Downloads](https://img.shields.io/endpoint?url=https://www.pinnumber.rr.nu/badges/downloads/yeongpin/cursor-free-vip/total)](https://github.com/yeongpin/cursor-free-vip/releases/latest) 14 | Buy Me a Coffee 15 | 16 |

17 | 18 | yeongpin%2Fcursor-free-vip | Trendshift 19 |
20 | 21 | Buy Me a Coffee 22 | 23 | 24 | 25 |

Support Latest 0.48.x Version | 支持最新 0.48.x 版本

26 | 27 | This tool is for educational purposes, currently the repo does not violate any laws. Please support the original project. 28 | This tool will not generate any fake email accounts and OAuth access. 29 | 30 | Supports Windows, macOS and Linux. 31 | 32 | For optimal performance, run with privileges and always stay up to date. 33 | 34 | 這是一款用於學習和研究的工具,目前 repo 沒有違反任何法律。請支持原作者。 35 | 這款工具不會生成任何假的電子郵件帳戶和 OAuth 訪問。 36 | 37 | 支持 Windows、macOS 和 Linux。 38 | 39 | 對於最佳性能,請以管理員身份運行並始終保持最新。 40 | 41 | 42 |

43 | new
44 |

45 | 46 |
47 | 48 | ## 🔄 Change Log | 更新日志 49 | 50 | [Watch Change Log | 查看更新日志](CHANGELOG.md) 51 | 52 | ## ✨ Features | 功能特點 53 | 54 | * Support Windows macOS and Linux systems
支持 Windows、macOS 和 Linux 系統
55 | 56 | * Reset Cursor's configuration
重置 Cursor 的配置
57 | 58 | * Multi-language support (English, 简体中文, 繁體中文, Vietnamese)
多語言支持(英文、简体中文、繁體中文、越南語)
59 | 60 | ## 💻 System Support | 系統支持 61 | 62 | | Operating System | Architecture | Supported | 63 | |------------------|-------------------|-----------| 64 | | Windows | x64, x86 | ✅ | 65 | | macOS | Intel, Apple Silicon | ✅ | 66 | | Linux | x64, x86, ARM64 | ✅ | 67 | 68 | ## 👀 How to use | 如何使用 69 | 70 |
71 | ⭐ Auto Run Script | 腳本自動化運行 72 | 73 | ### **Linux/macOS** 74 | 75 | ```bash 76 | curl -fsSL https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/install.sh -o install.sh && chmod +x install.sh && ./install.sh 77 | ``` 78 | 79 | ### **Archlinux** 80 | 81 | Install via [AUR](https://aur.archlinux.org/packages/cursor-free-vip-git) 82 | 83 | ```bash 84 | yay -S cursor-free-vip-git 85 | ``` 86 | 87 | ### **Windows** 88 | 89 | ```powershell 90 | irm https://raw.githubusercontent.com/yeongpin/cursor-free-vip/main/scripts/install.ps1 | iex 91 | ``` 92 | 93 |
94 | 95 | If you want to stop the script, please press Ctrl+C
要停止腳本,請按 Ctrl+C 96 | 97 | ## ❗ Note | 注意事項 98 | 99 | 📝 Config | 文件配置 100 | `Win / Macos / Linux Path | 路徑 [Documents/.cursor-free-vip/config.ini]` 101 |
102 | ⭐ Config | 文件配置 103 | 104 | ``` 105 | [Chrome] 106 | # Default Google Chrome Path | 默認Google Chrome 遊覽器路徑 107 | chromepath = C:\Program Files\Google/Chrome/Application/chrome.exe 108 | 109 | [Turnstile] 110 | # Handle Turnstile Wait Time | 等待人機驗證時間 111 | handle_turnstile_time = 2 112 | # Handle Turnstile Wait Random Time (must merge 1-3 or 1,3) | 等待人機驗證隨機時間(必須是 1-3 或者 1,3 這樣的組合) 113 | handle_turnstile_random_time = 1-3 114 | 115 | [OSPaths] 116 | # Storage Path | 存儲路徑 117 | storage_path = /Users/username/Library/Application Support/Cursor/User/globalStorage/storage.json 118 | # SQLite Path | SQLite路徑 119 | sqlite_path = /Users/username/Library/Application Support/Cursor/User/globalStorage/state.vscdb 120 | # Machine ID Path | 機器ID路徑 121 | machine_id_path = /Users/username/Library/Application Support/Cursor/machineId 122 | # For Linux users: ~/.config/cursor/machineid 123 | 124 | [Timing] 125 | # Min Random Time | 最小隨機時間 126 | min_random_time = 0.1 127 | # Max Random Time | 最大隨機時間 128 | max_random_time = 0.8 129 | # Page Load Wait | 頁面加載等待時間 130 | page_load_wait = 0.1-0.8 131 | # Input Wait | 輸入等待時間 132 | input_wait = 0.3-0.8 133 | # Submit Wait | 提交等待時間 134 | submit_wait = 0.5-1.5 135 | # Verification Code Input | 驗證碼輸入等待時間 136 | verification_code_input = 0.1-0.3 137 | # Verification Success Wait | 驗證成功等待時間 138 | verification_success_wait = 2-3 139 | # Verification Retry Wait | 驗證重試等待時間 140 | verification_retry_wait = 2-3 141 | # Email Check Initial Wait | 郵件檢查初始等待時間 142 | email_check_initial_wait = 4-6 143 | # Email Refresh Wait | 郵件刷新等待時間 144 | email_refresh_wait = 2-4 145 | # Settings Page Load Wait | 設置頁面加載等待時間 146 | settings_page_load_wait = 1-2 147 | # Failed Retry Time | 失敗重試時間 148 | failed_retry_time = 0.5-1 149 | # Retry Interval | 重試間隔 150 | retry_interval = 8-12 151 | # Max Timeout | 最大超時時間 152 | max_timeout = 160 153 | 154 | [Utils] 155 | # Check Update | 檢查更新 156 | check_update = True 157 | # Show Account Info | 顯示賬號信息 158 | show_account_info = True 159 | 160 | [WindowsPaths] 161 | storage_path = C:\Users\yeongpin\AppData\Roaming\Cursor\User\globalStorage\storage.json 162 | sqlite_path = C:\Users\yeongpin\AppData\Roaming\Cursor\User\globalStorage\state.vscdb 163 | machine_id_path = C:\Users\yeongpin\AppData\Roaming\Cursor\machineId 164 | cursor_path = C:\Users\yeongpin\AppData\Local\Programs\Cursor\resources\app 165 | updater_path = C:\Users\yeongpin\AppData\Local\cursor-updater 166 | update_yml_path = C:\Users\yeongpin\AppData\Local\Programs\Cursor\resources\app-update.yml 167 | product_json_path = C:\Users\yeongpin\AppData\Local\Programs\Cursor\resources\app\product.json 168 | 169 | [Browser] 170 | default_browser = opera 171 | chrome_path = C:\Program Files\Google\Chrome\Application\chrome.exe 172 | edge_path = C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe 173 | firefox_path = C:\Program Files\Mozilla Firefox\firefox.exe 174 | brave_path = C:\Program Files\BraveSoftware/Brave-Browser/Application/brave.exe 175 | chrome_driver_path = D:\VisualCode\cursor-free-vip-new\drivers\chromedriver.exe 176 | edge_driver_path = D:\VisualCode\cursor-free-vip-new\drivers\msedgedriver.exe 177 | firefox_driver_path = D:\VisualCode\cursor-free-vip-new\drivers\geckodriver.exe 178 | brave_driver_path = D:\VisualCode\cursor-free-vip-new\drivers\chromedriver.exe 179 | opera_path = C:\Users\yeongpin\AppData\Local\Programs\Opera\opera.exe 180 | opera_driver_path = D:\VisualCode\cursor-free-vip-new\drivers\chromedriver.exe 181 | 182 | [OAuth] 183 | show_selection_alert = False 184 | timeout = 120 185 | max_attempts = 3 186 | ``` 187 | 188 |
189 | 190 | * Use administrator privileges to run the script
請使用管理員身份運行腳本 191 | 192 | * Confirm that Cursor is closed before running the script
請確保在運行腳本前已經關閉 Cursor
193 | 194 | * This tool is only for learning and research purposes
此工具僅供學習和研究使用
195 | 196 | * Please comply with the relevant software usage terms when using this tool
使用本工具時請遵守相關軟件使用條款 197 | 198 | ## 🚨 Common Issues | 常見問題 199 | 200 | | 如果遇到權限問題,請確保: | 此腳本以管理員身份運行 | 201 | |:--------------------------------------------------:|:------------------------------------------------:| 202 | | If you encounter permission issues, please ensure: | This script is run with administrator privileges | 203 | | Error 'User is not authorized' | This means your account was banned for using temporary (disposal) mail. Ensure using a non-temporary mail service | 204 | ## 🤩 Contribution | 貢獻 205 | 206 | 歡迎提交 Issue 和 Pull Request! 207 | 208 | 209 | 210 | 211 | 212 |

213 | 214 | ## 📩 Disclaimer | 免責聲明 215 | 216 | 本工具僅供學習和研究使用,使用本工具所產生的任何後果由使用者自行承擔。
217 | 218 | This tool is only for learning and research purposes, and any consequences arising from the use of this tool are borne 219 | by the user. 220 | 221 | ## 💰 Buy Me a Coffee | 請我喝杯咖啡 222 | 223 |
224 | 225 | 226 | 229 | 232 | 233 |
227 | buy_me_a_coffee
228 |
230 | buy_me_a_coffee
231 |
234 |
235 | 236 | ## ⭐ Star History | 星星數 237 | 238 |
239 | 240 | [![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) 241 | 242 |
243 | 244 | ## 📝 License | 授權 245 | 246 | 本項目採用 [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/) 授權。 247 | Please refer to the [LICENSE](LICENSE.md) file for details. 248 | -------------------------------------------------------------------------------- /block_domain.txt: -------------------------------------------------------------------------------- 1 | 0-mail.com 2 | 10minemail.com 3 | 1secmail.com 4 | 20minutemail.com 5 | 2925.com 6 | 2prong.com 7 | 33mail.com 8 | abusemail.de 9 | afrobacon.com 10 | anonbox.net 11 | anonymbox.com 12 | antichef.com 13 | bareed.ws 14 | begemail.com 15 | boun.cr 16 | brefmail.com 17 | burnermail.io 18 | byom.de 19 | chammy.info 20 | cloud-mail.top 21 | cocovpn.com 22 | cool.fr.nf 23 | corhash.net 24 | crazymailing.com 25 | cuvox.de 26 | dayrep.com 27 | deadaddress.com 28 | discard.email 29 | dispostable.com 30 | drewzen.com 31 | dudmail.com 32 | dugmail.com 33 | emailondeck.com 34 | emailtemporario.com.br 35 | ephemail.net 36 | fakeinbox.com 37 | fakeinbox.org 38 | fakemailgenerator.com 39 | famamail.com 40 | fastmailbox.net 41 | filzmail.com 42 | fizmail.com 43 | getairmail.com 44 | getnada.com 45 | givmail.com 46 | guerrillamail.com 47 | gustr.com 48 | harakirimail.com 49 | hottempmail.com 50 | ikomail.com 51 | inboxbear.com 52 | inboxkitten.com 53 | incognitomail.org 54 | indigobook.com 55 | jetable.org 56 | kaspop.com 57 | letthemeatspam.com 58 | linshiyouxiang.net 59 | luxusmail.org 60 | mail-temp.com 61 | mail1a.de 62 | mailbucket.org 63 | mailcatch.com 64 | maildrop.cc 65 | mailexpire.com 66 | mailhazard.com 67 | mailimate.com 68 | mailin8r.com 69 | mailinator.com 70 | mailme.lv 71 | mailnesia.com 72 | mailnull.com 73 | mailpull.com 74 | mailsac.com 75 | mailshou.com 76 | mailtemp.net 77 | mailzilla.org 78 | meltmail.com 79 | mintemail.com 80 | moakt.com 81 | mohmal.com 82 | my10minutemail.com 83 | mycleaninbox.net 84 | mytrashmail.com 85 | no-spam.ws 86 | nomail.pw 87 | nospamfor.us 88 | notmailinator.com 89 | nowmymail.com 90 | oakon.com 91 | objectmail.com 92 | ofanda.com 93 | openmailbox.org 94 | owlpic.com 95 | pastryofistanbul.com 96 | privacyroot.com 97 | pusmail.com 98 | questtechsystems.com 99 | raleigh-construction.com 100 | rcpt.at 101 | safemail.link 102 | sendspamhere.com 103 | sharklasers.com 104 | shortmail.net 105 | solerbe.net 106 | spam4.me 107 | spamavert.com 108 | spambog.com 109 | spamdecoy.net 110 | spamex.com 111 | spamfree24.org 112 | spamgourmet.com 113 | spamhereplease.com 114 | spaml.com 115 | spamslicer.com 116 | spamsphere.com 117 | spamtroll.net 118 | teihu.com 119 | temp-mail.org 120 | tempmail.net 121 | tempmailaddress.com 122 | temporaryemail.net 123 | throwawayemail.com 124 | tmail.ws 125 | trash-mail.com 126 | trash2009.com 127 | trashdevil.com 128 | trashmail.com 129 | trashmail.de 130 | trbvn.com 131 | wegwerfadresse.org 132 | yepmail.net 133 | yopmail.com 134 | zippymail.info -------------------------------------------------------------------------------- /build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | chcp 65001 > nul 3 | cls 4 | 5 | :: Check if running with administrator privileges 6 | net session >nul 2>&1 7 | if %errorLevel% == 0 ( 8 | :: If running with administrator privileges, create virtual environment and then run with normal user privileges 9 | if not exist venv ( 10 | echo ℹ️ 正在創建虛擬環境... 11 | python -m venv venv 12 | ) 13 | 14 | :: Run remaining steps with normal user privileges 15 | echo ℹ️ 以普通用戶權限繼續... 16 | powershell -Command "Start-Process -FilePath '%comspec%' -ArgumentList '/c cd /d %cd% && %~f0 run' -Verb RunAs:NO" 17 | exit /b 18 | ) else ( 19 | :: Check if running in second stage 20 | if "%1"=="run" ( 21 | goto RUN_BUILD 22 | ) else ( 23 | :: If running with normal privileges and creating virtual environment is required, request administrator privileges 24 | if not exist venv ( 25 | echo ⚠️ Requires administrator privileges to create virtual environment 26 | echo ℹ️ Requesting administrator privileges... 27 | powershell -Command "Start-Process -Verb RunAs -FilePath '%comspec%' -ArgumentList '/c cd /d %cd% && %~f0'" 28 | exit /b 29 | ) else ( 30 | goto RUN_BUILD 31 | ) 32 | ) 33 | ) 34 | 35 | :RUN_BUILD 36 | echo ℹ️ Starting virtual environment... 37 | call venv\Scripts\activate.bat 38 | if errorlevel 1 ( 39 | echo ❌ Failed to start virtual environment 40 | pause 41 | exit /b 1 42 | ) 43 | 44 | :: Check and install missing dependencies 45 | echo ℹ️ Checking dependencies... 46 | for /f "tokens=1" %%i in (requirements.txt) do ( 47 | pip show %%i >nul 2>&1 || ( 48 | echo ℹ️ Installing %%i... 49 | pip install %%i 50 | ) 51 | ) 52 | 53 | echo ℹ️ Starting build... 54 | python build.py 55 | if errorlevel 1 ( 56 | echo ❌ Build failed 57 | pause 58 | exit /b 1 59 | ) 60 | 61 | echo ✅ Completed! 62 | pause -------------------------------------------------------------------------------- /build.mac.command: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export PYTHONWARNINGS=ignore::SyntaxWarning:DrissionPage 3 | 4 | # Get script directory 5 | cd "$(dirname "$0")" 6 | 7 | echo "Creating virtual environment..." 8 | 9 | # Check if virtual environment exists 10 | if [ ! -d "venv" ]; then 11 | python3 -m venv venv 12 | if [ $? -ne 0 ]; then 13 | echo "Failed to create virtual environment!" 14 | exit 1 15 | fi 16 | fi 17 | 18 | # Activate virtual environment 19 | source venv/bin/activate 20 | 21 | # Install dependencies 22 | echo "Installing dependencies..." 23 | python -m pip install --upgrade pip 24 | pip install -r requirements.txt 25 | 26 | # Run build script 27 | echo "Starting build process..." 28 | python build.py 29 | 30 | # Keep window open 31 | echo "Build completed!" 32 | echo "Press any key to exit..." 33 | read -n 1 -------------------------------------------------------------------------------- /build.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | import os 3 | import platform 4 | import subprocess 5 | import time 6 | import threading 7 | import shutil 8 | from logo import print_logo 9 | from dotenv import load_dotenv 10 | 11 | # Ignore specific warnings 12 | warnings.filterwarnings("ignore", category=SyntaxWarning) 13 | 14 | class LoadingAnimation: 15 | def __init__(self): 16 | self.is_running = False 17 | self.animation_thread = None 18 | 19 | def start(self, message="Building"): 20 | self.is_running = True 21 | self.animation_thread = threading.Thread(target=self._animate, args=(message,)) 22 | self.animation_thread.start() 23 | 24 | def stop(self): 25 | self.is_running = False 26 | if self.animation_thread: 27 | self.animation_thread.join() 28 | print("\r" + " " * 70 + "\r", end="", flush=True) 29 | 30 | def _animate(self, message): 31 | animation = "|/-\\" 32 | idx = 0 33 | while self.is_running: 34 | print(f"\r{message} {animation[idx % len(animation)]}", end="", flush=True) 35 | idx += 1 36 | time.sleep(0.1) 37 | 38 | def progress_bar(progress, total, prefix="", length=50): 39 | filled = int(length * progress // total) 40 | bar = "█" * filled + "░" * (length - filled) 41 | percent = f"{100 * progress / total:.1f}" 42 | print(f"\r{prefix} |{bar}| {percent}% Complete", end="", flush=True) 43 | if progress == total: 44 | print() 45 | 46 | def simulate_progress(message, duration=1.0, steps=20): 47 | print(f"\033[94m{message}\033[0m") 48 | for i in range(steps + 1): 49 | time.sleep(duration / steps) 50 | progress_bar(i, steps, prefix="Progress:", length=40) 51 | 52 | def build(): 53 | # Clean screen 54 | os.system("cls" if platform.system().lower() == "windows" else "clear") 55 | 56 | # Display logo 57 | print_logo() 58 | 59 | # Clean PyInstaller cache 60 | print("\033[93m🧹 Cleaning build cache...\033[0m") 61 | if os.path.exists('build'): 62 | shutil.rmtree('build') 63 | 64 | # Reload environment variables to ensure getting the latest version 65 | load_dotenv(override=True) 66 | version = os.getenv('VERSION', '1.0.0') 67 | print(f"\033[93m📦 Building version: v{version}\033[0m") 68 | 69 | try: 70 | simulate_progress("Preparing build environment...", 0.5) 71 | 72 | loading = LoadingAnimation() 73 | loading.start("Building in progress") 74 | 75 | # Set output name based on system type 76 | system = platform.system().lower() 77 | if system == "windows": 78 | os_type = "windows" 79 | ext = ".exe" 80 | elif system == "linux": 81 | os_type = "linux" 82 | ext = "" 83 | else: # Darwin 84 | os_type = "mac" 85 | ext = "" 86 | 87 | output_name = f"CursorFreeVIP_{version}_{os_type}" 88 | 89 | # Build command 90 | build_command = f'pyinstaller --clean --noconfirm build.spec' 91 | output_path = os.path.join('dist', f'{output_name}{ext}') 92 | 93 | os.system(build_command) 94 | 95 | loading.stop() 96 | 97 | if os.path.exists(output_path): 98 | print(f"\n\033[92m✅ Build completed!") 99 | print(f"📦 Executable file located: {output_path}\033[0m") 100 | else: 101 | print("\n\033[91m❌ Build failed: Output file not found\033[0m") 102 | return False 103 | 104 | except Exception as e: 105 | if loading: 106 | loading.stop() 107 | print(f"\n\033[91m❌ Build process error: {str(e)}\033[0m") 108 | return False 109 | 110 | return True 111 | 112 | if __name__ == "__main__": 113 | build() -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 颜色定义 4 | RED='\033[0;31m' 5 | GREEN='\033[0;32m' 6 | YELLOW='\033[1;33m' 7 | NC='\033[0m' # No Color 8 | 9 | # 检查并安装必要的依赖 10 | check_dependencies() { 11 | echo -e "${YELLOW}Checking system dependencies...${NC}" 12 | 13 | # 检查是否为 Ubuntu/Debian 14 | if [ -f /etc/debian_version ]; then 15 | # 检查并安装必要的包 16 | PACKAGES="python3 python3-pip python3-venv" 17 | for pkg in $PACKAGES; do 18 | if ! dpkg -l | grep -q "^ii $pkg "; then 19 | echo -e "${YELLOW}Installing $pkg...${NC}" 20 | sudo apt-get update 21 | sudo apt-get install -y $pkg 22 | fi 23 | done 24 | else 25 | echo -e "${RED}Unsupported system, please install python3, pip3 and python3-venv manually${NC}" 26 | exit 1 27 | fi 28 | } 29 | 30 | # 创建并激活虚拟环境 31 | setup_venv() { 32 | echo -e "${GREEN}Creating virtual environment...${NC}" 33 | python3 -m venv venv 34 | 35 | echo -e "${GREEN}Starting virtual environment...${NC}" 36 | . ./venv/bin/activate || source ./venv/bin/activate 37 | } 38 | 39 | # 安装依赖 40 | install_dependencies() { 41 | echo -e "${GREEN}Installing dependencies...${NC}" 42 | python3 -m pip install --upgrade pip 43 | pip3 install -r requirements.txt 44 | } 45 | 46 | # 构建程序 47 | build_program() { 48 | echo -e "${GREEN}Starting build...${NC}" 49 | python3 build.py 50 | } 51 | 52 | # 清理 53 | cleanup() { 54 | echo -e "${GREEN}Cleaning virtual environment...${NC}" 55 | deactivate 2>/dev/null || true 56 | rm -rf venv 57 | } 58 | 59 | # 主程序 60 | main() { 61 | # 检查依赖 62 | check_dependencies 63 | 64 | # 设置虚拟环境 65 | setup_venv 66 | 67 | # 安装依赖 68 | install_dependencies 69 | 70 | # 构建 71 | build_program 72 | 73 | # 清理 74 | cleanup 75 | 76 | echo -e "${GREEN}Completed!${NC}" 77 | echo "Press any key to exit..." 78 | # 使用兼容的方式读取输入 79 | if [ "$(uname)" = "Linux" ]; then 80 | read dummy 81 | else 82 | read -n 1 83 | fi 84 | } 85 | 86 | # 运行主程序 87 | main -------------------------------------------------------------------------------- /build.spec: -------------------------------------------------------------------------------- 1 | # -*- mode: python ; coding: utf-8 -*- 2 | import os 3 | import platform 4 | from dotenv import load_dotenv 5 | 6 | # 加載環境變量獲取版本號 7 | load_dotenv() 8 | version = os.getenv('VERSION', '1.0.0') 9 | 10 | # 根据系统类型设置输出名称 11 | system = platform.system().lower() 12 | if system == "windows": 13 | os_type = "windows" 14 | elif system == "linux": 15 | os_type = "linux" 16 | else: # Darwin 17 | os_type = "mac" 18 | 19 | output_name = f"CursorFreeVIP_{version}_{os_type}" 20 | 21 | a = Analysis( 22 | ['main.py'], 23 | pathex=[], 24 | binaries=[], 25 | datas=[ 26 | ('locales', 'locales'), 27 | ('quit_cursor.py', '.'), 28 | ('utils.py', '.'), 29 | ('.env', '.') 30 | ], 31 | hiddenimports=[ 32 | 'quit_cursor', 33 | 'utils' 34 | ], 35 | hookspath=[], 36 | hooksconfig={}, 37 | runtime_hooks=[], 38 | excludes=[], 39 | noarchive=False, 40 | ) 41 | 42 | pyz = PYZ(a.pure) 43 | 44 | target_arch = os.environ.get('TARGET_ARCH', None) 45 | 46 | exe = EXE( 47 | pyz, 48 | a.scripts, 49 | a.binaries, 50 | a.datas, 51 | [], 52 | name=output_name, # 使用动态生成的名称 53 | debug=False, 54 | bootloader_ignore_signals=False, 55 | strip=False, 56 | upx=False, 57 | upx_exclude=[], 58 | runtime_tmpdir=None, 59 | console=True, 60 | disable_windowed_traceback=False, 61 | argv_emulation=True, # 对非Mac平台无影响 62 | target_arch=target_arch, # 仅在需要时通过环境变量指定 63 | codesign_identity=None, 64 | entitlements_file=None, 65 | icon=None 66 | ) -------------------------------------------------------------------------------- /bypass_token_limit.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import platform 4 | import tempfile 5 | import glob 6 | from colorama import Fore, Style, init 7 | import configparser 8 | import sys 9 | from config import get_config 10 | from datetime import datetime 11 | 12 | # Initialize colorama 13 | init() 14 | 15 | # Define emoji constants 16 | EMOJI = { 17 | "FILE": "📄", 18 | "BACKUP": "💾", 19 | "SUCCESS": "✅", 20 | "ERROR": "❌", 21 | "INFO": "ℹ️", 22 | "RESET": "🔄", 23 | "WARNING": "⚠️", 24 | } 25 | 26 | def get_user_documents_path(): 27 | """Get user Documents folder path""" 28 | if sys.platform == "win32": 29 | return os.path.join(os.path.expanduser("~"), "Documents") 30 | elif sys.platform == "darwin": 31 | return os.path.join(os.path.expanduser("~"), "Documents") 32 | else: # Linux 33 | # Get actual user's home directory 34 | sudo_user = os.environ.get('SUDO_USER') 35 | if sudo_user: 36 | return os.path.join("/home", sudo_user, "Documents") 37 | return os.path.join(os.path.expanduser("~"), "Documents") 38 | 39 | 40 | def get_workbench_cursor_path(translator=None) -> str: 41 | """Get Cursor workbench.desktop.main.js path""" 42 | system = platform.system() 43 | 44 | # Read configuration 45 | config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip") 46 | config_file = os.path.join(config_dir, "config.ini") 47 | config = configparser.ConfigParser() 48 | 49 | if os.path.exists(config_file): 50 | config.read(config_file) 51 | 52 | paths_map = { 53 | "Darwin": { # macOS 54 | "base": "/Applications/Cursor.app/Contents/Resources/app", 55 | "main": "out/vs/workbench/workbench.desktop.main.js" 56 | }, 57 | "Windows": { 58 | "main": "out\\vs\\workbench\\workbench.desktop.main.js" 59 | }, 60 | "Linux": { 61 | "bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app", "/usr/lib/cursor/app/"], 62 | "main": "out/vs/workbench/workbench.desktop.main.js" 63 | } 64 | } 65 | 66 | if system == "Linux": 67 | # Add extracted AppImage with correct usr structure 68 | extracted_usr_paths = glob.glob(os.path.expanduser("~/squashfs-root/usr/share/cursor/resources/app")) 69 | 70 | paths_map["Linux"]["bases"].extend(extracted_usr_paths) 71 | 72 | if system not in paths_map: 73 | raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}") 74 | 75 | if system == "Linux": 76 | for base in paths_map["Linux"]["bases"]: 77 | main_path = os.path.join(base, paths_map["Linux"]["main"]) 78 | print(f"{Fore.CYAN}{EMOJI['INFO']} Checking path: {main_path}{Style.RESET_ALL}") 79 | if os.path.exists(main_path): 80 | return main_path 81 | 82 | if system == "Windows": 83 | base_path = config.get('WindowsPaths', 'cursor_path') 84 | elif system == "Darwin": 85 | base_path = paths_map[system]["base"] 86 | if config.has_section('MacPaths') and config.has_option('MacPaths', 'cursor_path'): 87 | base_path = config.get('MacPaths', 'cursor_path') 88 | else: # Linux 89 | # For Linux, we've already checked all bases in the loop above 90 | # If we're here, it means none of the bases worked, so we'll use the first one 91 | base_path = paths_map[system]["bases"][0] 92 | if config.has_section('LinuxPaths') and config.has_option('LinuxPaths', 'cursor_path'): 93 | base_path = config.get('LinuxPaths', 'cursor_path') 94 | 95 | main_path = os.path.join(base_path, paths_map[system]["main"]) 96 | 97 | if not os.path.exists(main_path): 98 | raise OSError(translator.get('reset.file_not_found', path=main_path) if translator else f"未找到 Cursor main.js 文件: {main_path}") 99 | 100 | return main_path 101 | 102 | 103 | def modify_workbench_js(file_path: str, translator=None) -> bool: 104 | """ 105 | Modify file content 106 | """ 107 | try: 108 | # Save original file permissions 109 | original_stat = os.stat(file_path) 110 | original_mode = original_stat.st_mode 111 | original_uid = original_stat.st_uid 112 | original_gid = original_stat.st_gid 113 | 114 | # Create temporary file 115 | with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", errors="ignore", delete=False) as tmp_file: 116 | # Read original content 117 | with open(file_path, "r", encoding="utf-8", errors="ignore") as main_file: 118 | content = main_file.read() 119 | 120 | patterns = { 121 | # 通用按钮替换模式 122 | r'B(k,D(Ln,{title:"Upgrade to Pro",size:"small",get codicon(){return A.rocket},get onClick(){return t.pay}}),null)': r'B(k,D(Ln,{title:"yeongpin GitHub",size:"small",get codicon(){return A.github},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)', 123 | 124 | # Windows/Linux 125 | r'M(x,I(as,{title:"Upgrade to Pro",size:"small",get codicon(){return $.rocket},get onClick(){return t.pay}}),null)': r'M(x,I(as,{title:"yeongpin GitHub",size:"small",get codicon(){return $.github},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)', 126 | 127 | # Mac 通用按钮替换模式 128 | r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)': 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)', 129 | # Badge 替换 130 | r'
Pro Trial': r'
Pro', 131 | 132 | r'py-1">Auto-select': r'py-1">Bypass-Version-Pin', 133 | 134 | # 135 | r'async getEffectiveTokenLimit(e){const n=e.modelName;if(!n)return 2e5;':r'async getEffectiveTokenLimit(e){return 9000000;const n=e.modelName;if(!n)return 9e5;', 136 | # Pro 137 | r'var DWr=ne("
You are currently signed in with .");': r'var DWr=ne("
You are currently signed in with .

Pro

");', 138 | 139 | # Toast 替换 140 | r'notifications-toasts': r'notifications-toasts hidden' 141 | } 142 | 143 | # 使用patterns进行替换 144 | for old_pattern, new_pattern in patterns.items(): 145 | content = content.replace(old_pattern, new_pattern) 146 | 147 | # Write to temporary file 148 | tmp_file.write(content) 149 | tmp_path = tmp_file.name 150 | 151 | # Backup original file with timestamp 152 | timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") 153 | backup_path = f"{file_path}.backup.{timestamp}" 154 | shutil.copy2(file_path, backup_path) 155 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.backup_created', path=backup_path)}{Style.RESET_ALL}") 156 | 157 | # Move temporary file to original position 158 | if os.path.exists(file_path): 159 | os.remove(file_path) 160 | shutil.move(tmp_path, file_path) 161 | 162 | # Restore original permissions 163 | os.chmod(file_path, original_mode) 164 | if os.name != "nt": # Not Windows 165 | os.chown(file_path, original_uid, original_gid) 166 | 167 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.file_modified')}{Style.RESET_ALL}") 168 | return True 169 | 170 | except Exception as e: 171 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}") 172 | if "tmp_path" in locals(): 173 | try: 174 | os.unlink(tmp_path) 175 | except: 176 | pass 177 | return False 178 | 179 | def run(translator=None): 180 | config = get_config(translator) 181 | if not config: 182 | return False 183 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") 184 | print(f"{Fore.CYAN}{EMOJI['RESET']} {translator.get('bypass_token_limit.title')}{Style.RESET_ALL}") 185 | print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}") 186 | 187 | modify_workbench_js(get_workbench_cursor_path(translator), translator) 188 | 189 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") 190 | input(f"{EMOJI['INFO']} {translator.get('bypass_token_limit.press_enter')}...") 191 | 192 | if __name__ == "__main__": 193 | from main import translator as main_translator 194 | run(main_translator) -------------------------------------------------------------------------------- /bypass_version.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import shutil 4 | import platform 5 | import configparser 6 | import time 7 | from colorama import Fore, Style, init 8 | import sys 9 | import traceback 10 | from utils import get_user_documents_path 11 | 12 | # Initialize colorama 13 | init() 14 | 15 | # Define emoji constants 16 | EMOJI = { 17 | 'INFO': 'ℹ️', 18 | 'SUCCESS': '✅', 19 | 'ERROR': '❌', 20 | 'WARNING': '⚠️', 21 | 'FILE': '📄', 22 | 'BACKUP': '💾', 23 | 'RESET': '🔄', 24 | 'VERSION': '🏷️' 25 | } 26 | 27 | def get_product_json_path(translator=None): 28 | """Get Cursor product.json path""" 29 | system = platform.system() 30 | 31 | # Read configuration 32 | config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip") 33 | config_file = os.path.join(config_dir, "config.ini") 34 | config = configparser.ConfigParser() 35 | 36 | if os.path.exists(config_file): 37 | config.read(config_file) 38 | 39 | if system == "Windows": 40 | localappdata = os.environ.get("LOCALAPPDATA") 41 | if not localappdata: 42 | raise OSError(translator.get('bypass.localappdata_not_found') if translator else "LOCALAPPDATA environment variable not found") 43 | 44 | product_json_path = os.path.join(localappdata, "Programs", "Cursor", "resources", "app", "product.json") 45 | 46 | # Check if path exists in config 47 | if 'WindowsPaths' in config and 'cursor_path' in config['WindowsPaths']: 48 | cursor_path = config.get('WindowsPaths', 'cursor_path') 49 | product_json_path = os.path.join(cursor_path, "product.json") 50 | 51 | elif system == "Darwin": # macOS 52 | product_json_path = "/Applications/Cursor.app/Contents/Resources/app/product.json" 53 | if config.has_section('MacPaths') and config.has_option('MacPaths', 'product_json_path'): 54 | product_json_path = config.get('MacPaths', 'product_json_path') 55 | 56 | elif system == "Linux": 57 | # Try multiple common paths 58 | possible_paths = [ 59 | "/opt/Cursor/resources/app/product.json", 60 | "/usr/share/cursor/resources/app/product.json", 61 | "/usr/lib/cursor/app/product.json" 62 | ] 63 | 64 | # Add extracted AppImage paths 65 | extracted_usr_paths = os.path.expanduser("~/squashfs-root/usr/share/cursor/resources/app/product.json") 66 | if os.path.exists(extracted_usr_paths): 67 | possible_paths.append(extracted_usr_paths) 68 | 69 | for path in possible_paths: 70 | if os.path.exists(path): 71 | product_json_path = path 72 | break 73 | else: 74 | raise OSError(translator.get('bypass.product_json_not_found') if translator else "product.json not found in common Linux paths") 75 | 76 | else: 77 | raise OSError(translator.get('bypass.unsupported_os', system=system) if translator else f"Unsupported operating system: {system}") 78 | 79 | if not os.path.exists(product_json_path): 80 | raise OSError(translator.get('bypass.file_not_found', path=product_json_path) if translator else f"File not found: {product_json_path}") 81 | 82 | return product_json_path 83 | 84 | def compare_versions(version1, version2): 85 | """Compare two version strings""" 86 | v1_parts = [int(x) for x in version1.split('.')] 87 | v2_parts = [int(x) for x in version2.split('.')] 88 | 89 | for i in range(max(len(v1_parts), len(v2_parts))): 90 | v1 = v1_parts[i] if i < len(v1_parts) else 0 91 | v2 = v2_parts[i] if i < len(v2_parts) else 0 92 | if v1 < v2: 93 | return -1 94 | elif v1 > v2: 95 | return 1 96 | 97 | return 0 98 | 99 | def bypass_version(translator=None): 100 | """Bypass Cursor version check by modifying product.json""" 101 | try: 102 | print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('bypass.starting') if translator else 'Starting Cursor version bypass...'}{Style.RESET_ALL}") 103 | 104 | # Get product.json path 105 | product_json_path = get_product_json_path(translator) 106 | print(f"{Fore.CYAN}{EMOJI['FILE']} {translator.get('bypass.found_product_json', path=product_json_path) if translator else f'Found product.json: {product_json_path}'}{Style.RESET_ALL}") 107 | 108 | # Check file permissions 109 | if not os.access(product_json_path, os.W_OK): 110 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.no_write_permission', path=product_json_path) if translator else f'No write permission for file: {product_json_path}'}{Style.RESET_ALL}") 111 | return False 112 | 113 | # Read product.json 114 | try: 115 | with open(product_json_path, "r", encoding="utf-8") as f: 116 | product_data = json.load(f) 117 | except Exception as e: 118 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.read_failed', error=str(e)) if translator else f'Failed to read product.json: {str(e)}'}{Style.RESET_ALL}") 119 | return False 120 | 121 | # Get current version 122 | current_version = product_data.get("version", "0.0.0") 123 | print(f"{Fore.CYAN}{EMOJI['VERSION']} {translator.get('bypass.current_version', version=current_version) if translator else f'Current version: {current_version}'}{Style.RESET_ALL}") 124 | 125 | # Check if version needs to be modified 126 | if compare_versions(current_version, "0.46.0") < 0: 127 | # Create backup 128 | timestamp = time.strftime("%Y%m%d%H%M%S") 129 | backup_path = f"{product_json_path}.{timestamp}" 130 | shutil.copy2(product_json_path, backup_path) 131 | print(f"{Fore.GREEN}{EMOJI['BACKUP']} {translator.get('bypass.backup_created', path=backup_path) if translator else f'Backup created: {backup_path}'}{Style.RESET_ALL}") 132 | 133 | # Modify version 134 | new_version = "0.48.7" 135 | product_data["version"] = new_version 136 | 137 | # Save modified product.json 138 | try: 139 | with open(product_json_path, "w", encoding="utf-8") as f: 140 | json.dump(product_data, f, indent=2) 141 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('bypass.version_updated', old=current_version, new=new_version) if translator else f'Version updated from {current_version} to {new_version}'}{Style.RESET_ALL}") 142 | return True 143 | except Exception as e: 144 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.write_failed', error=str(e)) if translator else f'Failed to write product.json: {str(e)}'}{Style.RESET_ALL}") 145 | return False 146 | else: 147 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('bypass.no_update_needed', version=current_version) if translator else f'No update needed. Current version {current_version} is already >= 0.46.0'}{Style.RESET_ALL}") 148 | return True 149 | 150 | except Exception as e: 151 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.bypass_failed', error=str(e)) if translator else f'Version bypass failed: {str(e)}'}{Style.RESET_ALL}") 152 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('bypass.stack_trace') if translator else 'Stack trace'}: {traceback.format_exc()}{Style.RESET_ALL}") 153 | return False 154 | 155 | def main(translator=None): 156 | """Main function""" 157 | return bypass_version(translator) 158 | 159 | if __name__ == "__main__": 160 | main() -------------------------------------------------------------------------------- /check_user_authorized.py: -------------------------------------------------------------------------------- 1 | import os 2 | import requests 3 | import time 4 | import hashlib 5 | import base64 6 | import struct 7 | from colorama import Fore, Style, init 8 | 9 | # Initialize colorama 10 | init() 11 | 12 | # Define emoji constants 13 | EMOJI = { 14 | "SUCCESS": "✅", 15 | "ERROR": "❌", 16 | "INFO": "ℹ️", 17 | "WARNING": "⚠️", 18 | "KEY": "🔑", 19 | "CHECK": "🔍" 20 | } 21 | 22 | def generate_hashed64_hex(input_str: str, salt: str = '') -> str: 23 | """Generate a SHA-256 hash of input + salt and return as hex""" 24 | hash_obj = hashlib.sha256() 25 | hash_obj.update((input_str + salt).encode('utf-8')) 26 | return hash_obj.hexdigest() 27 | 28 | def obfuscate_bytes(byte_array: bytearray) -> bytearray: 29 | """Obfuscate bytes using the algorithm from utils.js""" 30 | t = 165 31 | for r in range(len(byte_array)): 32 | byte_array[r] = ((byte_array[r] ^ t) + (r % 256)) & 0xFF 33 | t = byte_array[r] 34 | return byte_array 35 | 36 | def generate_cursor_checksum(token: str, translator=None) -> str: 37 | """Generate Cursor checksum from token using the algorithm""" 38 | try: 39 | # Clean the token 40 | clean_token = token.strip() 41 | 42 | # Generate machineId and macMachineId 43 | machine_id = generate_hashed64_hex(clean_token, 'machineId') 44 | mac_machine_id = generate_hashed64_hex(clean_token, 'macMachineId') 45 | 46 | # Get timestamp and convert to byte array 47 | timestamp = int(time.time() * 1000) // 1000000 48 | byte_array = bytearray(struct.pack('>Q', timestamp)[-6:]) # Take last 6 bytes 49 | 50 | # Obfuscate bytes and encode as base64 51 | obfuscated_bytes = obfuscate_bytes(byte_array) 52 | encoded_checksum = base64.b64encode(obfuscated_bytes).decode('utf-8') 53 | 54 | # Combine final checksum 55 | return f"{encoded_checksum}{machine_id}/{mac_machine_id}" 56 | except Exception as e: 57 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.error_generating_checksum', error=str(e)) if translator else f'Error generating checksum: {str(e)}'}{Style.RESET_ALL}") 58 | return "" 59 | 60 | def check_user_authorized(token: str, translator=None) -> bool: 61 | """ 62 | Check if the user is authorized with the given token 63 | 64 | Args: 65 | token (str): The authorization token 66 | translator: Optional translator for internationalization 67 | 68 | Returns: 69 | bool: True if authorized, False otherwise 70 | """ 71 | try: 72 | print(f"{Fore.CYAN}{EMOJI['CHECK']} {translator.get('auth_check.checking_authorization') if translator else 'Checking authorization...'}{Style.RESET_ALL}") 73 | 74 | # Clean the token 75 | if token and '%3A%3A' in token: 76 | token = token.split('%3A%3A')[1] 77 | elif token and '::' in token: 78 | token = token.split('::')[1] 79 | 80 | # Remove any whitespace 81 | token = token.strip() 82 | 83 | if not token or len(token) < 10: # Add a basic validation for token length 84 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.invalid_token') if translator else 'Invalid token'}{Style.RESET_ALL}") 85 | return False 86 | 87 | print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.token_length', length=len(token)) if translator else f'Token length: {len(token)} characters'}{Style.RESET_ALL}") 88 | 89 | # Try to get usage info using the DashboardService API 90 | try: 91 | # Generate checksum 92 | checksum = generate_cursor_checksum(token, translator) 93 | 94 | # Create request headers 95 | headers = { 96 | 'accept-encoding': 'gzip', 97 | 'authorization': f'Bearer {token}', 98 | 'connect-protocol-version': '1', 99 | 'content-type': 'application/proto', 100 | 'user-agent': 'connect-es/1.6.1', 101 | 'x-cursor-checksum': checksum, 102 | 'x-cursor-client-version': '0.48.7', 103 | 'x-cursor-timezone': 'Asia/Shanghai', 104 | 'x-ghost-mode': 'false', 105 | 'Host': 'api2.cursor.sh' 106 | } 107 | 108 | print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.checking_usage_information') if translator else 'Checking usage information...'}{Style.RESET_ALL}") 109 | 110 | # Make the request - this endpoint doesn't need a request body 111 | usage_response = requests.post( 112 | 'https://api2.cursor.sh/aiserver.v1.DashboardService/GetUsageBasedPremiumRequests', 113 | headers=headers, 114 | data=b'', # Empty body 115 | timeout=10 116 | ) 117 | 118 | print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.usage_response', response=usage_response.status_code) if translator else f'Usage response status: {usage_response.status_code}'}{Style.RESET_ALL}") 119 | 120 | if usage_response.status_code == 200: 121 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('auth_check.user_authorized') if translator else 'User is authorized'}{Style.RESET_ALL}") 122 | return True 123 | elif usage_response.status_code == 401 or usage_response.status_code == 403: 124 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.user_unauthorized') if translator else 'User is unauthorized'}{Style.RESET_ALL}") 125 | return False 126 | else: 127 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.unexpected_status_code', code=usage_response.status_code) if translator else f'Unexpected status code: {usage_response.status_code}'}{Style.RESET_ALL}") 128 | 129 | # If the token at least looks like a valid JWT, consider it valid 130 | if token.startswith('eyJ') and '.' in token and len(token) > 100: 131 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.jwt_token_warning') if translator else 'Token appears to be in JWT format, but API check returned an unexpected status code. The token might be valid but API access is restricted.'}{Style.RESET_ALL}") 132 | return True 133 | 134 | return False 135 | except Exception as e: 136 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} Error checking usage: {str(e)}{Style.RESET_ALL}") 137 | 138 | # If the token at least looks like a valid JWT, consider it valid even if the API check fails 139 | if token.startswith('eyJ') and '.' in token and len(token) > 100: 140 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.jwt_token_warning') if translator else 'Token appears to be in JWT format, but API check failed. The token might be valid but API access is restricted.'}{Style.RESET_ALL}") 141 | return True 142 | 143 | return False 144 | 145 | except requests.exceptions.Timeout: 146 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.request_timeout') if translator else 'Request timed out'}{Style.RESET_ALL}") 147 | return False 148 | except requests.exceptions.ConnectionError: 149 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.connection_error') if translator else 'Connection error'}{Style.RESET_ALL}") 150 | return False 151 | except Exception as e: 152 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.check_error', error=str(e)) if translator else f'Error checking authorization: {str(e)}'}{Style.RESET_ALL}") 153 | return False 154 | 155 | def run(translator=None): 156 | """Run function to be called from main.py""" 157 | try: 158 | # Ask user if they want to get token from database or input manually 159 | choice = input(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.token_source') if translator else 'Get token from database or input manually? (d/m, default: d): '}{Style.RESET_ALL}").strip().lower() 160 | 161 | token = None 162 | 163 | # If user chooses database or default 164 | if not choice or choice == 'd': 165 | print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.getting_token_from_db') if translator else 'Getting token from database...'}{Style.RESET_ALL}") 166 | 167 | try: 168 | # Import functions from cursor_acc_info.py 169 | from cursor_acc_info import get_token 170 | 171 | # Get token using the get_token function 172 | token = get_token() 173 | 174 | if token: 175 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('auth_check.token_found_in_db') if translator else 'Token found in database'}{Style.RESET_ALL}") 176 | else: 177 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.token_not_found_in_db') if translator else 'Token not found in database'}{Style.RESET_ALL}") 178 | except ImportError: 179 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.cursor_acc_info_not_found') if translator else 'cursor_acc_info.py not found'}{Style.RESET_ALL}") 180 | except Exception as e: 181 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.error_getting_token_from_db', error=str(e)) if translator else f'Error getting token from database: {str(e)}'}{Style.RESET_ALL}") 182 | 183 | # If token not found in database or user chooses manual input 184 | if not token: 185 | # Try to get token from environment 186 | token = os.environ.get('CURSOR_TOKEN') 187 | 188 | # If not in environment, ask user to input 189 | if not token: 190 | token = input(f"{Fore.CYAN}{EMOJI['KEY']} {translator.get('auth_check.enter_token') if translator else 'Enter your Cursor token: '}{Style.RESET_ALL}") 191 | 192 | # Check authorization 193 | is_authorized = check_user_authorized(token, translator) 194 | 195 | if is_authorized: 196 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('auth_check.authorization_successful') if translator else 'Authorization successful!'}{Style.RESET_ALL}") 197 | else: 198 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.authorization_failed') if translator else 'Authorization failed!'}{Style.RESET_ALL}") 199 | 200 | return is_authorized 201 | 202 | except KeyboardInterrupt: 203 | print(f"\n{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.operation_cancelled') if translator else 'Operation cancelled by user'}{Style.RESET_ALL}") 204 | return False 205 | except Exception as e: 206 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.unexpected_error', error=str(e)) if translator else f'Unexpected error: {str(e)}'}{Style.RESET_ALL}") 207 | return False 208 | 209 | def main(translator=None): 210 | """Main function to check user authorization""" 211 | return run(translator) 212 | 213 | if __name__ == "__main__": 214 | main() -------------------------------------------------------------------------------- /cursor_auth.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import os 3 | import sys 4 | from colorama import Fore, Style, init 5 | from config import get_config 6 | 7 | # Initialize colorama 8 | init() 9 | 10 | # Define emoji and color constants 11 | EMOJI = { 12 | 'DB': '🗄️', 13 | 'UPDATE': '🔄', 14 | 'SUCCESS': '✅', 15 | 'ERROR': '❌', 16 | 'WARN': '⚠️', 17 | 'INFO': 'ℹ️', 18 | 'FILE': '📄', 19 | 'KEY': '🔐' 20 | } 21 | 22 | class CursorAuth: 23 | def __init__(self, translator=None): 24 | self.translator = translator 25 | 26 | # Get configuration 27 | config = get_config(translator) 28 | if not config: 29 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.config_error') if self.translator else 'Failed to load configuration'}{Style.RESET_ALL}") 30 | sys.exit(1) 31 | 32 | # Get path based on operating system 33 | try: 34 | if sys.platform == "win32": # Windows 35 | if not config.has_section('WindowsPaths'): 36 | raise ValueError("Windows paths not configured") 37 | self.db_path = config.get('WindowsPaths', 'sqlite_path') 38 | 39 | elif sys.platform == 'linux': # Linux 40 | if not config.has_section('LinuxPaths'): 41 | raise ValueError("Linux paths not configured") 42 | self.db_path = config.get('LinuxPaths', 'sqlite_path') 43 | 44 | elif sys.platform == 'darwin': # macOS 45 | if not config.has_section('MacPaths'): 46 | raise ValueError("macOS paths not configured") 47 | self.db_path = config.get('MacPaths', 'sqlite_path') 48 | 49 | else: 50 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.unsupported_platform') if self.translator else 'Unsupported platform'}{Style.RESET_ALL}") 51 | sys.exit(1) 52 | 53 | # Verify if the path exists 54 | if not os.path.exists(os.path.dirname(self.db_path)): 55 | raise FileNotFoundError(f"Database directory not found: {os.path.dirname(self.db_path)}") 56 | 57 | except Exception as e: 58 | 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}") 59 | sys.exit(1) 60 | 61 | # Check if the database file exists 62 | if not os.path.exists(self.db_path): 63 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_not_found', path=self.db_path)}{Style.RESET_ALL}") 64 | return 65 | 66 | # Check file permissions 67 | if not os.access(self.db_path, os.R_OK | os.W_OK): 68 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_permission_error')}{Style.RESET_ALL}") 69 | return 70 | 71 | try: 72 | self.conn = sqlite3.connect(self.db_path) 73 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('auth.connected_to_database')}{Style.RESET_ALL}") 74 | except sqlite3.Error as e: 75 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.db_connection_error', error=str(e))}{Style.RESET_ALL}") 76 | return 77 | 78 | def update_auth(self, email=None, access_token=None, refresh_token=None): 79 | conn = None 80 | try: 81 | # Ensure the directory exists and set the correct permissions 82 | db_dir = os.path.dirname(self.db_path) 83 | if not os.path.exists(db_dir): 84 | os.makedirs(db_dir, mode=0o755, exist_ok=True) 85 | 86 | # If the database file does not exist, create a new one 87 | if not os.path.exists(self.db_path): 88 | conn = sqlite3.connect(self.db_path) 89 | cursor = conn.cursor() 90 | cursor.execute(''' 91 | CREATE TABLE IF NOT EXISTS ItemTable ( 92 | key TEXT PRIMARY KEY, 93 | value TEXT 94 | ) 95 | ''') 96 | conn.commit() 97 | if sys.platform != "win32": 98 | os.chmod(self.db_path, 0o644) 99 | conn.close() 100 | 101 | # Reconnect to the database 102 | conn = sqlite3.connect(self.db_path) 103 | print(f"{EMOJI['INFO']} {Fore.GREEN} {self.translator.get('auth.connected_to_database')}{Style.RESET_ALL}") 104 | cursor = conn.cursor() 105 | 106 | # Add timeout and other optimization settings 107 | conn.execute("PRAGMA busy_timeout = 5000") 108 | conn.execute("PRAGMA journal_mode = WAL") 109 | conn.execute("PRAGMA synchronous = NORMAL") 110 | 111 | # Set the key-value pairs to update 112 | updates = [] 113 | 114 | updates.append(("cursorAuth/cachedSignUpType", "Auth_0")) 115 | 116 | if email is not None: 117 | updates.append(("cursorAuth/cachedEmail", email)) 118 | if access_token is not None: 119 | updates.append(("cursorAuth/accessToken", access_token)) 120 | if refresh_token is not None: 121 | updates.append(("cursorAuth/refreshToken", refresh_token)) 122 | 123 | 124 | # Use transactions to ensure data integrity 125 | cursor.execute("BEGIN TRANSACTION") 126 | try: 127 | for key, value in updates: 128 | # Check if the key exists 129 | cursor.execute("SELECT COUNT(*) FROM ItemTable WHERE key = ?", (key,)) 130 | if cursor.fetchone()[0] == 0: 131 | cursor.execute(""" 132 | INSERT INTO ItemTable (key, value) 133 | VALUES (?, ?) 134 | """, (key, value)) 135 | else: 136 | cursor.execute(""" 137 | UPDATE ItemTable SET value = ? 138 | WHERE key = ? 139 | """, (value, key)) 140 | print(f"{EMOJI['INFO']} {Fore.CYAN} {self.translator.get('auth.updating_pair')} {key.split('/')[-1]}...{Style.RESET_ALL}") 141 | 142 | cursor.execute("COMMIT") 143 | print(f"{EMOJI['SUCCESS']} {Fore.GREEN}{self.translator.get('auth.database_updated_successfully')}{Style.RESET_ALL}") 144 | return True 145 | 146 | except Exception as e: 147 | cursor.execute("ROLLBACK") 148 | raise e 149 | 150 | except sqlite3.Error as e: 151 | print(f"\n{EMOJI['ERROR']} {Fore.RED} {self.translator.get('auth.database_error', error=str(e))}{Style.RESET_ALL}") 152 | return False 153 | except Exception as e: 154 | print(f"\n{EMOJI['ERROR']} {Fore.RED} {self.translator.get('auth.an_error_occurred', error=str(e))}{Style.RESET_ALL}") 155 | return False 156 | finally: 157 | if conn: 158 | conn.close() 159 | print(f"{EMOJI['DB']} {Fore.CYAN} {self.translator.get('auth.database_connection_closed')}{Style.RESET_ALL}") -------------------------------------------------------------------------------- /cursor_register_manual.py: -------------------------------------------------------------------------------- 1 | import os 2 | from colorama import Fore, Style, init 3 | import time 4 | import random 5 | from faker import Faker 6 | from cursor_auth import CursorAuth 7 | from reset_machine_manual import MachineIDResetter 8 | from get_user_token import get_token_from_cookie 9 | 10 | os.environ["PYTHONVERBOSE"] = "0" 11 | os.environ["PYINSTALLER_VERBOSE"] = "0" 12 | 13 | # Initialize colorama 14 | init() 15 | 16 | # Define emoji constants 17 | EMOJI = { 18 | 'START': '🚀', 19 | 'FORM': '📝', 20 | 'VERIFY': '🔄', 21 | 'PASSWORD': '🔑', 22 | 'CODE': '📱', 23 | 'DONE': '✨', 24 | 'ERROR': '❌', 25 | 'WAIT': '⏳', 26 | 'SUCCESS': '✅', 27 | 'MAIL': '📧', 28 | 'KEY': '🔐', 29 | 'UPDATE': '🔄', 30 | 'INFO': 'ℹ️' 31 | } 32 | 33 | class CursorRegistration: 34 | def __init__(self, translator=None): 35 | self.translator = translator 36 | # Set to display mode 37 | os.environ['BROWSER_HEADLESS'] = 'False' 38 | self.browser = None 39 | self.controller = None 40 | self.sign_up_url = "https://authenticator.cursor.sh/sign-up" 41 | self.settings_url = "https://www.cursor.com/settings" 42 | self.email_address = None 43 | self.signup_tab = None 44 | self.email_tab = None 45 | 46 | # initialize Faker instance 47 | self.faker = Faker() 48 | 49 | # generate account information 50 | self.password = self._generate_password() 51 | self.first_name = self.faker.first_name() 52 | self.last_name = self.faker.last_name() 53 | 54 | # modify the first letter of the first name(keep the original function) 55 | new_first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ") 56 | self.first_name = new_first_letter + self.first_name[1:] 57 | 58 | print(f"\n{Fore.CYAN}{EMOJI['PASSWORD']} {self.translator.get('register.password')}: {self.password} {Style.RESET_ALL}") 59 | print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.first_name')}: {self.first_name} {Style.RESET_ALL}") 60 | print(f"{Fore.CYAN}{EMOJI['FORM']} {self.translator.get('register.last_name')}: {self.last_name} {Style.RESET_ALL}") 61 | 62 | def _generate_password(self, length=12): 63 | """Generate password""" 64 | return self.faker.password(length=length, special_chars=True, digits=True, upper_case=True, lower_case=True) 65 | 66 | def setup_email(self): 67 | """Setup Email""" 68 | try: 69 | print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.manual_email_input') if self.translator else 'Please enter your email address:'}") 70 | self.email_address = input().strip() 71 | 72 | if '@' not in self.email_address: 73 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.invalid_email') if self.translator else '无效的邮箱地址'}{Style.RESET_ALL}") 74 | return False 75 | 76 | print(f"{Fore.CYAN}{EMOJI['MAIL']} {self.translator.get('register.email_address')}: {self.email_address}" + "\n" + f"{Style.RESET_ALL}") 77 | return True 78 | 79 | except Exception as e: 80 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.email_setup_failed', error=str(e))}{Style.RESET_ALL}") 81 | return False 82 | 83 | def get_verification_code(self): 84 | """Manually Get Verification Code""" 85 | try: 86 | print(f"{Fore.CYAN}{EMOJI['CODE']} {self.translator.get('register.manual_code_input') if self.translator else 'Please enter the verification code:'}") 87 | code = input().strip() 88 | 89 | if not code.isdigit() or len(code) != 6: 90 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.invalid_code') if self.translator else '无效的验证码'}{Style.RESET_ALL}") 91 | return None 92 | 93 | return code 94 | 95 | except Exception as e: 96 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.code_input_failed', error=str(e))}{Style.RESET_ALL}") 97 | return None 98 | 99 | def register_cursor(self): 100 | """Register Cursor""" 101 | browser_tab = None 102 | try: 103 | print(f"{Fore.CYAN}{EMOJI['START']} {self.translator.get('register.register_start')}...{Style.RESET_ALL}") 104 | 105 | # Use new_signup.py directly for registration 106 | from new_signup import main as new_signup_main 107 | 108 | # Execute new registration process, passing translator 109 | result, browser_tab = new_signup_main( 110 | email=self.email_address, 111 | password=self.password, 112 | first_name=self.first_name, 113 | last_name=self.last_name, 114 | email_tab=None, # No email tab needed 115 | controller=self, # Pass self instead of self.controller 116 | translator=self.translator 117 | ) 118 | 119 | if result: 120 | # Use the returned browser instance to get account information 121 | self.signup_tab = browser_tab # Save browser instance 122 | success = self._get_account_info() 123 | 124 | # Close browser after getting information 125 | if browser_tab: 126 | try: 127 | browser_tab.quit() 128 | except: 129 | pass 130 | 131 | return success 132 | 133 | return False 134 | 135 | except Exception as e: 136 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.register_process_error', error=str(e))}{Style.RESET_ALL}") 137 | return False 138 | finally: 139 | # Ensure browser is closed in any case 140 | if browser_tab: 141 | try: 142 | browser_tab.quit() 143 | except: 144 | pass 145 | 146 | def _get_account_info(self): 147 | """Get Account Information and Token""" 148 | try: 149 | self.signup_tab.get(self.settings_url) 150 | time.sleep(2) 151 | 152 | usage_selector = ( 153 | "css:div.col-span-2 > div > div > div > div > " 154 | "div:nth-child(1) > div.flex.items-center.justify-between.gap-2 > " 155 | "span.font-mono.text-sm\\/\\[0\\.875rem\\]" 156 | ) 157 | usage_ele = self.signup_tab.ele(usage_selector) 158 | total_usage = "未知" 159 | if usage_ele: 160 | total_usage = usage_ele.text.split("/")[-1].strip() 161 | 162 | print(f"Total Usage: {total_usage}\n") 163 | print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('register.get_token')}...{Style.RESET_ALL}") 164 | max_attempts = 30 165 | retry_interval = 2 166 | attempts = 0 167 | 168 | while attempts < max_attempts: 169 | try: 170 | cookies = self.signup_tab.cookies() 171 | for cookie in cookies: 172 | if cookie.get("name") == "WorkosCursorSessionToken": 173 | token = get_token_from_cookie(cookie["value"], self.translator) 174 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.token_success')}{Style.RESET_ALL}") 175 | self._save_account_info(token, total_usage) 176 | return True 177 | 178 | attempts += 1 179 | if attempts < max_attempts: 180 | print(f"{Fore.YELLOW}{EMOJI['WAIT']} {self.translator.get('register.token_attempt', attempt=attempts, time=retry_interval)}{Style.RESET_ALL}") 181 | time.sleep(retry_interval) 182 | else: 183 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.token_max_attempts', max=max_attempts)}{Style.RESET_ALL}") 184 | 185 | except Exception as e: 186 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.token_failed', error=str(e))}{Style.RESET_ALL}") 187 | attempts += 1 188 | if attempts < max_attempts: 189 | print(f"{Fore.YELLOW}{EMOJI['WAIT']} {self.translator.get('register.token_attempt', attempt=attempts, time=retry_interval)}{Style.RESET_ALL}") 190 | time.sleep(retry_interval) 191 | 192 | return False 193 | 194 | except Exception as e: 195 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.account_error', error=str(e))}{Style.RESET_ALL}") 196 | return False 197 | 198 | def _save_account_info(self, token, total_usage): 199 | """Save Account Information to File""" 200 | try: 201 | # Update authentication information first 202 | print(f"{Fore.CYAN}{EMOJI['KEY']} {self.translator.get('register.update_cursor_auth_info')}...{Style.RESET_ALL}") 203 | if self.update_cursor_auth(email=self.email_address, access_token=token, refresh_token=token): 204 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.cursor_auth_info_updated')}...{Style.RESET_ALL}") 205 | else: 206 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.cursor_auth_info_update_failed')}...{Style.RESET_ALL}") 207 | 208 | # Reset machine ID 209 | print(f"{Fore.CYAN}{EMOJI['UPDATE']} {self.translator.get('register.reset_machine_id')}...{Style.RESET_ALL}") 210 | resetter = MachineIDResetter(self.translator) # Create instance with translator 211 | if not resetter.reset_machine_ids(): # Call reset_machine_ids method directly 212 | raise Exception("Failed to reset machine ID") 213 | 214 | # Save account information to file 215 | with open('cursor_accounts.txt', 'a', encoding='utf-8') as f: 216 | f.write(f"\n{'='*50}\n") 217 | f.write(f"Email: {self.email_address}\n") 218 | f.write(f"Password: {self.password}\n") 219 | f.write(f"Token: {token}\n") 220 | f.write(f"Usage Limit: {total_usage}\n") 221 | f.write(f"{'='*50}\n") 222 | 223 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('register.account_info_saved')}...{Style.RESET_ALL}") 224 | return True 225 | 226 | except Exception as e: 227 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('register.save_account_info_failed', error=str(e))}{Style.RESET_ALL}") 228 | return False 229 | 230 | def start(self): 231 | """Start Registration Process""" 232 | try: 233 | if self.setup_email(): 234 | if self.register_cursor(): 235 | print(f"\n{Fore.GREEN}{EMOJI['DONE']} {self.translator.get('register.cursor_registration_completed')}...{Style.RESET_ALL}") 236 | return True 237 | return False 238 | finally: 239 | # Close email tab 240 | if hasattr(self, 'temp_email'): 241 | try: 242 | self.temp_email.close() 243 | except: 244 | pass 245 | 246 | def update_cursor_auth(self, email=None, access_token=None, refresh_token=None): 247 | """Convenient function to update Cursor authentication information""" 248 | auth_manager = CursorAuth(translator=self.translator) 249 | return auth_manager.update_auth(email, access_token, refresh_token) 250 | 251 | def main(translator=None): 252 | """Main function to be called from main.py""" 253 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") 254 | print(f"{Fore.CYAN}{EMOJI['START']} {translator.get('register.title')}{Style.RESET_ALL}") 255 | print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}") 256 | 257 | registration = CursorRegistration(translator) 258 | registration.start() 259 | 260 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") 261 | input(f"{EMOJI['INFO']} {translator.get('register.press_enter')}...") 262 | 263 | if __name__ == "__main__": 264 | from main import translator as main_translator 265 | main(main_translator) -------------------------------------------------------------------------------- /delete_cursor_google.py: -------------------------------------------------------------------------------- 1 | from oauth_auth import OAuthHandler 2 | import time 3 | from colorama import Fore, Style, init 4 | import sys 5 | 6 | # Initialize colorama 7 | init() 8 | 9 | # Define emoji constants 10 | EMOJI = { 11 | 'START': '🚀', 12 | 'DELETE': '🗑️', 13 | 'SUCCESS': '✅', 14 | 'ERROR': '❌', 15 | 'WAIT': '⏳', 16 | 'INFO': 'ℹ️', 17 | 'WARNING': '⚠️' 18 | } 19 | 20 | class CursorGoogleAccountDeleter(OAuthHandler): 21 | def __init__(self, translator=None): 22 | super().__init__(translator, auth_type='google') 23 | 24 | def delete_google_account(self): 25 | """Delete Cursor account using Google OAuth""" 26 | try: 27 | # Setup browser and select profile 28 | if not self.setup_browser(): 29 | return False 30 | 31 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.starting_process') if self.translator else 'Starting account deletion process...'}{Style.RESET_ALL}") 32 | 33 | # Navigate to Cursor auth page - using the same URL as in registration 34 | self.browser.get("https://authenticator.cursor.sh/sign-up") 35 | time.sleep(2) 36 | 37 | # Click Google auth button using same selectors as in registration 38 | selectors = [ 39 | "//a[contains(@href,'GoogleOAuth')]", 40 | "//a[contains(@class,'auth-method-button') and contains(@href,'GoogleOAuth')]", 41 | "(//a[contains(@class,'auth-method-button')])[1]" # First auth button as fallback 42 | ] 43 | 44 | auth_btn = None 45 | for selector in selectors: 46 | try: 47 | auth_btn = self.browser.ele(f"xpath:{selector}", timeout=2) 48 | if auth_btn: 49 | break 50 | except: 51 | continue 52 | 53 | if not auth_btn: 54 | raise Exception(self.translator.get('account_delete.google_button_not_found') if self.translator else "Google login button not found") 55 | 56 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.logging_in') if self.translator else 'Logging in with Google...'}{Style.RESET_ALL}") 57 | auth_btn.click() 58 | 59 | # Wait for authentication to complete using a more robust method 60 | print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('account_delete.waiting_for_auth', fallback='Waiting for Google authentication...')}{Style.RESET_ALL}") 61 | 62 | # Dynamic wait for authentication 63 | max_wait_time = 120 # Increase maximum wait time to 120 seconds 64 | start_time = time.time() 65 | check_interval = 3 # Check every 3 seconds 66 | google_account_alert_shown = False # Track if we've shown the alert already 67 | 68 | while time.time() - start_time < max_wait_time: 69 | current_url = self.browser.url 70 | 71 | # If we're already on the settings or dashboard page, we're successful 72 | if "/dashboard" in current_url or "/settings" in current_url or "cursor.com" in current_url: 73 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.login_successful') if self.translator else 'Login successful'}{Style.RESET_ALL}") 74 | break 75 | 76 | # If we're on Google accounts page or accounts.google.com, wait for user selection 77 | if "accounts.google.com" in current_url: 78 | # Only show the alert once to avoid spamming 79 | if not google_account_alert_shown: 80 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.select_google_account', fallback='Please select your Google account...')}{Style.RESET_ALL}") 81 | # Alert to indicate user action needed 82 | try: 83 | self.browser.run_js(""" 84 | alert('Please select your Google account to continue with Cursor authentication'); 85 | """) 86 | google_account_alert_shown = True # Mark that we've shown the alert 87 | except: 88 | pass # Alert is optional 89 | 90 | # Sleep before checking again 91 | time.sleep(check_interval) 92 | else: 93 | # If the loop completed without breaking, it means we hit the timeout 94 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.auth_timeout', fallback='Authentication timeout, continuing anyway...')}{Style.RESET_ALL}") 95 | 96 | # Check current URL to determine next steps 97 | current_url = self.browser.url 98 | 99 | # If we're already on the settings page, no need to navigate 100 | if "/settings" in current_url and "cursor.com" in current_url: 101 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.already_on_settings', fallback='Already on settings page')}{Style.RESET_ALL}") 102 | # If we're on the dashboard or any Cursor page but not settings, navigate to settings 103 | elif "cursor.com" in current_url or "authenticator.cursor.sh" in current_url: 104 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.navigating_to_settings', fallback='Navigating to settings page...')}{Style.RESET_ALL}") 105 | self.browser.get("https://www.cursor.com/settings") 106 | # If we're still on Google auth or somewhere else, try directly going to settings 107 | else: 108 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.login_redirect_failed', fallback='Login redirection failed, trying direct navigation...')}{Style.RESET_ALL}") 109 | self.browser.get("https://www.cursor.com/settings") 110 | 111 | # Wait for the settings page to load 112 | time.sleep(3) # Reduced from 5 seconds 113 | 114 | # First look for the email element to confirm we're logged in 115 | try: 116 | email_element = self.browser.ele("css:div[class='flex w-full flex-col gap-2'] div:nth-child(2) p:nth-child(2)") 117 | if email_element: 118 | email = email_element.text 119 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.found_email', email=email, fallback=f'Found email: {email}')}{Style.RESET_ALL}") 120 | except Exception as e: 121 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.email_not_found', error=str(e), fallback=f'Email not found: {str(e)}')}{Style.RESET_ALL}") 122 | 123 | # Click on "Advanced" tab or dropdown - keep only the successful approach 124 | advanced_found = False 125 | 126 | # Direct JavaScript querySelector approach that worked according to logs 127 | try: 128 | advanced_element_js = self.browser.run_js(""" 129 | // Try to find the Advanced dropdown using querySelector with the exact classes 130 | let advancedElement = document.querySelector('div.mb-0.flex.cursor-pointer.items-center.text-xs:not([style*="display: none"])'); 131 | 132 | // If not found, try a more general approach 133 | if (!advancedElement) { 134 | const allDivs = document.querySelectorAll('div'); 135 | for (const div of allDivs) { 136 | if (div.textContent.includes('Advanced') && 137 | div.className.includes('mb-0') && 138 | div.className.includes('flex') && 139 | div.className.includes('cursor-pointer')) { 140 | advancedElement = div; 141 | break; 142 | } 143 | } 144 | } 145 | 146 | // Click the element if found 147 | if (advancedElement) { 148 | advancedElement.click(); 149 | return true; 150 | } 151 | 152 | return false; 153 | """) 154 | 155 | if advanced_element_js: 156 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.advanced_tab_clicked', fallback='Found and clicked Advanced using direct JavaScript selector')}{Style.RESET_ALL}") 157 | advanced_found = True 158 | time.sleep(1) # Reduced from 2 seconds 159 | except Exception as e: 160 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.advanced_tab_error', error=str(e), fallback='JavaScript querySelector approach failed: {str(e)}')}{Style.RESET_ALL}") 161 | 162 | if not advanced_found: 163 | # Fallback to direct URL navigation which is faster and more reliable 164 | try: 165 | self.browser.get("https://www.cursor.com/settings?tab=advanced") 166 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('account_delete.direct_advanced_navigation', fallback='Trying direct navigation to advanced tab')}{Style.RESET_ALL}") 167 | advanced_found = True 168 | except: 169 | raise Exception(self.translator.get('account_delete.advanced_tab_not_found') if self.translator else "Advanced option not found after multiple attempts") 170 | 171 | # Wait for dropdown/tab content to load 172 | time.sleep(2) # Reduced from 4 seconds 173 | 174 | # Find and click the "Delete Account" button 175 | delete_button_found = False 176 | 177 | # Simplified approach for delete button based on what worked 178 | delete_button_selectors = [ 179 | 'xpath://button[contains(., "Delete Account")]', 180 | 'xpath://button[text()="Delete Account"]', 181 | 'xpath://div[contains(text(), "Delete Account")]', 182 | 'xpath://button[contains(text(), "Delete") and contains(text(), "Account")]' 183 | ] 184 | 185 | for selector in delete_button_selectors: 186 | try: 187 | delete_button = self.browser.ele(selector, timeout=2) 188 | if delete_button: 189 | delete_button.click() 190 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('account_delete.delete_button_clicked') if self.translator else 'Clicked on Delete Account button'}{Style.RESET_ALL}") 191 | delete_button_found = True 192 | break 193 | except: 194 | continue 195 | 196 | if not delete_button_found: 197 | raise Exception(self.translator.get('account_delete.delete_button_not_found') if self.translator else "Delete Account button not found") 198 | 199 | # Wait for confirmation dialog to appear 200 | time.sleep(2) 201 | 202 | # Check if we need to input "Delete" at all - some modals might not require it 203 | input_required = True 204 | try: 205 | # Try detecting if the DELETE button is already enabled 206 | delete_button_enabled = self.browser.run_js(""" 207 | const buttons = Array.from(document.querySelectorAll('button')); 208 | const deleteButtons = buttons.filter(btn => 209 | btn.textContent.trim() === 'DELETE' || 210 | btn.textContent.trim() === 'Delete' 211 | ); 212 | 213 | if (deleteButtons.length > 0) { 214 | return !deleteButtons.some(btn => btn.disabled); 215 | } 216 | return false; 217 | """) 218 | 219 | if delete_button_enabled: 220 | print(f"{Fore.CYAN}{EMOJI['INFO']} DELETE button appears to be enabled already. Input may not be required.{Style.RESET_ALL}") 221 | input_required = False 222 | except: 223 | pass 224 | 225 | # Type "Delete" in the confirmation input - only if required 226 | delete_input_found = False 227 | 228 | if input_required: 229 | # Try common selectors for the input field 230 | delete_input_selectors = [ 231 | 'xpath://input[@placeholder="Delete"]', 232 | 'xpath://div[contains(@class, "modal")]//input', 233 | 'xpath://input', 234 | 'css:input' 235 | ] 236 | 237 | for selector in delete_input_selectors: 238 | try: 239 | delete_input = self.browser.ele(selector, timeout=3) 240 | if delete_input: 241 | delete_input.clear() 242 | delete_input.input("Delete") 243 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.typed_delete', fallback='Typed \"Delete\" in confirmation box')}{Style.RESET_ALL}") 244 | delete_input_found = True 245 | time.sleep(2) 246 | break 247 | except: 248 | # Try direct JavaScript input as fallback 249 | try: 250 | self.browser.run_js(r""" 251 | arguments[0].value = "Delete"; 252 | const event = new Event('input', { bubbles: true }); 253 | arguments[0].dispatchEvent(event); 254 | const changeEvent = new Event('change', { bubbles: true }); 255 | arguments[0].dispatchEvent(changeEvent); 256 | """, delete_input) 257 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.typed_delete_js', fallback='Typed \"Delete\" using JavaScript')}{Style.RESET_ALL}") 258 | delete_input_found = True 259 | time.sleep(2) 260 | break 261 | except: 262 | continue 263 | 264 | if not delete_input_found: 265 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('account_delete.delete_input_not_found', fallback='Delete confirmation input not found, continuing anyway')}{Style.RESET_ALL}") 266 | time.sleep(2) 267 | 268 | # Wait before clicking the final DELETE button 269 | time.sleep(2) 270 | 271 | # Click on the final DELETE button 272 | confirm_button_found = False 273 | 274 | # Use JavaScript approach for the DELETE button 275 | try: 276 | delete_button_js = self.browser.run_js(""" 277 | // Try to find the DELETE button by exact text content 278 | const buttons = Array.from(document.querySelectorAll('button')); 279 | const deleteButton = buttons.find(btn => 280 | btn.textContent.trim() === 'DELETE' || 281 | btn.textContent.trim() === 'Delete' 282 | ); 283 | 284 | if (deleteButton) { 285 | console.log("Found DELETE button with JavaScript"); 286 | deleteButton.click(); 287 | return true; 288 | } 289 | 290 | // If not found by text, try to find right-most button in the modal 291 | const modalButtons = Array.from(document.querySelectorAll('.relative button, [role="dialog"] button, .modal button, [aria-modal="true"] button')); 292 | 293 | if (modalButtons.length > 1) { 294 | modalButtons.sort((a, b) => { 295 | const rectA = a.getBoundingClientRect(); 296 | const rectB = b.getBoundingClientRect(); 297 | return rectB.right - rectA.right; 298 | }); 299 | 300 | console.log("Clicking right-most button in modal"); 301 | modalButtons[0].click(); 302 | return true; 303 | } else if (modalButtons.length === 1) { 304 | console.log("Clicking single button found in modal"); 305 | modalButtons[0].click(); 306 | return true; 307 | } 308 | 309 | return false; 310 | """) 311 | 312 | if delete_button_js: 313 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.delete_button_clicked', fallback='Clicked DELETE button')}{Style.RESET_ALL}") 314 | confirm_button_found = True 315 | except: 316 | pass 317 | 318 | if not confirm_button_found: 319 | # Fallback to simple selectors 320 | delete_button_selectors = [ 321 | 'xpath://button[text()="DELETE"]', 322 | 'xpath://div[contains(@class, "modal")]//button[last()]' 323 | ] 324 | 325 | for selector in delete_button_selectors: 326 | try: 327 | delete_button = self.browser.ele(selector, timeout=2) 328 | if delete_button: 329 | delete_button.click() 330 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('account_delete.delete_button_clicked', fallback='Account deleted successfully!')}{Style.RESET_ALL}") 331 | confirm_button_found = True 332 | break 333 | except: 334 | continue 335 | 336 | if not confirm_button_found: 337 | raise Exception(self.translator.get('account_delete.confirm_button_not_found') if self.translator else "Confirm button not found") 338 | 339 | # Wait a moment to see the confirmation 340 | time.sleep(2) 341 | 342 | return True 343 | 344 | except Exception as e: 345 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('account_delete.error', error=str(e)) if self.translator else f'Error during account deletion: {str(e)}'}{Style.RESET_ALL}") 346 | return False 347 | finally: 348 | # Clean up browser 349 | if self.browser: 350 | try: 351 | self.browser.quit() 352 | except: 353 | pass 354 | 355 | def main(translator=None): 356 | """Main function to handle Google account deletion""" 357 | print(f"\n{Fore.CYAN}{EMOJI['START']} {translator.get('account_delete.title') if translator else 'Cursor Google Account Deletion Tool'}{Style.RESET_ALL}") 358 | print(f"{Fore.YELLOW}{'─' * 50}{Style.RESET_ALL}") 359 | 360 | deleter = CursorGoogleAccountDeleter(translator) 361 | 362 | try: 363 | # Ask for confirmation 364 | print(f"{Fore.RED}{EMOJI['WARNING']} {translator.get('account_delete.warning') if translator else 'WARNING: This will permanently delete your Cursor account. This action cannot be undone.'}{Style.RESET_ALL}") 365 | confirm = input(f"{Fore.RED} {translator.get('account_delete.confirm_prompt') if translator else 'Are you sure you want to proceed? (y/N): '}{Style.RESET_ALL}").lower() 366 | 367 | if confirm != 'y': 368 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('account_delete.cancelled') if translator else 'Account deletion cancelled.'}{Style.RESET_ALL}") 369 | return 370 | 371 | success = deleter.delete_google_account() 372 | 373 | if success: 374 | print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('account_delete.success') if translator else 'Your Cursor account has been successfully deleted!'}{Style.RESET_ALL}") 375 | else: 376 | print(f"\n{Fore.RED}{EMOJI['ERROR']} {translator.get('account_delete.failed') if translator else 'Account deletion process failed or was cancelled.'}{Style.RESET_ALL}") 377 | 378 | except KeyboardInterrupt: 379 | print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('account_delete.interrupted') if translator else 'Account deletion process interrupted by user.'}{Style.RESET_ALL}") 380 | except Exception as e: 381 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('account_delete.unexpected_error', error=str(e)) if translator else f'Unexpected error: {str(e)}'}{Style.RESET_ALL}") 382 | finally: 383 | print(f"{Fore.YELLOW}{'─' * 50}{Style.RESET_ALL}") 384 | 385 | if __name__ == "__main__": 386 | main() -------------------------------------------------------------------------------- /disable_auto_update.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import platform 4 | import shutil 5 | from colorama import Fore, Style, init 6 | import subprocess 7 | from config import get_config 8 | import re 9 | import tempfile 10 | 11 | # Initialize colorama 12 | init() 13 | 14 | # Define emoji constants 15 | EMOJI = { 16 | "PROCESS": "🔄", 17 | "SUCCESS": "✅", 18 | "ERROR": "❌", 19 | "INFO": "ℹ️", 20 | "FOLDER": "📁", 21 | "FILE": "📄", 22 | "STOP": "🛑", 23 | "CHECK": "✔️" 24 | } 25 | 26 | class AutoUpdateDisabler: 27 | def __init__(self, translator=None): 28 | self.translator = translator 29 | self.system = platform.system() 30 | 31 | # Get path from configuration file 32 | config = get_config(translator) 33 | if config: 34 | if self.system == "Windows": 35 | self.updater_path = config.get('WindowsPaths', 'updater_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater")) 36 | self.update_yml_path = config.get('WindowsPaths', 'update_yml_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "update.yml")) 37 | self.product_json_path = config.get('WindowsPaths', 'product_json_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "product.json")) 38 | elif self.system == "Darwin": 39 | self.updater_path = config.get('MacPaths', 'updater_path', fallback=os.path.expanduser("~/Library/Application Support/cursor-updater")) 40 | self.update_yml_path = config.get('MacPaths', 'update_yml_path', fallback="/Applications/Cursor.app/Contents/Resources/app-update.yml") 41 | self.product_json_path = config.get('MacPaths', 'product_json_path', fallback="/Applications/Cursor.app/Contents/Resources/app/product.json") 42 | elif self.system == "Linux": 43 | self.updater_path = config.get('LinuxPaths', 'updater_path', fallback=os.path.expanduser("~/.config/cursor-updater")) 44 | self.update_yml_path = config.get('LinuxPaths', 'update_yml_path', fallback=os.path.expanduser("~/.config/cursor/resources/app-update.yml")) 45 | self.product_json_path = config.get('LinuxPaths', 'product_json_path', fallback=os.path.expanduser("~/.config/cursor/resources/app/product.json")) 46 | else: 47 | # If configuration loading fails, use default paths 48 | self.updater_paths = { 49 | "Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"), 50 | "Darwin": os.path.expanduser("~/Library/Application Support/cursor-updater"), 51 | "Linux": os.path.expanduser("~/.config/cursor-updater") 52 | } 53 | self.updater_path = self.updater_paths.get(self.system) 54 | 55 | self.update_yml_paths = { 56 | "Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "update.yml"), 57 | "Darwin": "/Applications/Cursor.app/Contents/Resources/app-update.yml", 58 | "Linux": os.path.expanduser("~/.config/cursor/resources/app-update.yml") 59 | } 60 | self.update_yml_path = self.update_yml_paths.get(self.system) 61 | 62 | self.product_json_paths = { 63 | "Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app", "product.json"), 64 | "Darwin": "/Applications/Cursor.app/Contents/Resources/app/product.json", 65 | "Linux": os.path.expanduser("~/.config/cursor/resources/app/product.json") 66 | } 67 | self.product_json_path = self.product_json_paths.get(self.system) 68 | 69 | def _remove_update_url(self): 70 | """Remove update URL""" 71 | try: 72 | original_stat = os.stat(self.product_json_path) 73 | original_mode = original_stat.st_mode 74 | original_uid = original_stat.st_uid 75 | original_gid = original_stat.st_gid 76 | 77 | with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_file: 78 | with open(self.product_json_path, "r", encoding="utf-8") as product_json_file: 79 | content = product_json_file.read() 80 | 81 | patterns = { 82 | r"https://api2.cursor.sh/aiserver.v1.AuthService/DownloadUpdate": r"", 83 | r"https://api2.cursor.sh/updates": r"", 84 | r"http://cursorapi.com/updates": r"", 85 | } 86 | 87 | for pattern, replacement in patterns.items(): 88 | content = re.sub(pattern, replacement, content) 89 | 90 | tmp_file.write(content) 91 | tmp_path = tmp_file.name 92 | 93 | shutil.copy2(self.product_json_path, self.product_json_path + ".old") 94 | shutil.move(tmp_path, self.product_json_path) 95 | 96 | os.chmod(self.product_json_path, original_mode) 97 | if os.name != "nt": 98 | os.chown(self.product_json_path, original_uid, original_gid) 99 | 100 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('reset.file_modified')}{Style.RESET_ALL}") 101 | return True 102 | 103 | except Exception as e: 104 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}") 105 | if "tmp_path" in locals(): 106 | os.unlink(tmp_path) 107 | return False 108 | 109 | def _kill_cursor_processes(self): 110 | """End all Cursor processes""" 111 | try: 112 | print(f"{Fore.CYAN}{EMOJI['PROCESS']} {self.translator.get('update.killing_processes') if self.translator else '正在结束 Cursor 进程...'}{Style.RESET_ALL}") 113 | 114 | if self.system == "Windows": 115 | subprocess.run(['taskkill', '/F', '/IM', 'Cursor.exe', '/T'], capture_output=True) 116 | else: 117 | subprocess.run(['pkill', '-f', 'Cursor'], capture_output=True) 118 | 119 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.processes_killed') if self.translator else 'Cursor 进程已结束'}{Style.RESET_ALL}") 120 | return True 121 | 122 | except Exception as e: 123 | 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}") 124 | return False 125 | 126 | def _remove_updater_directory(self): 127 | """Delete updater directory""" 128 | try: 129 | updater_path = self.updater_path 130 | if not updater_path: 131 | raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}") 132 | 133 | print(f"{Fore.CYAN}{EMOJI['FOLDER']} {self.translator.get('update.removing_directory') if self.translator else '正在删除更新程序目录...'}{Style.RESET_ALL}") 134 | 135 | if os.path.exists(updater_path): 136 | try: 137 | if os.path.isdir(updater_path): 138 | shutil.rmtree(updater_path) 139 | else: 140 | os.remove(updater_path) 141 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.directory_removed') if self.translator else '更新程序目录已删除'}{Style.RESET_ALL}") 142 | except PermissionError: 143 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.directory_locked', path=updater_path) if self.translator else f'更新程序目录已被锁定,跳过删除: {updater_path}'}{Style.RESET_ALL}") 144 | return True 145 | 146 | except Exception as e: 147 | 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}") 148 | return True 149 | 150 | def _clear_update_yml_file(self): 151 | """Clear update.yml file""" 152 | try: 153 | update_yml_path = self.update_yml_path 154 | if not update_yml_path: 155 | raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}") 156 | 157 | print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('update.clearing_update_yml') if self.translator else '正在清空更新配置文件...'}{Style.RESET_ALL}") 158 | 159 | if os.path.exists(update_yml_path): 160 | try: 161 | with open(update_yml_path, 'w') as f: 162 | f.write('') 163 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.update_yml_cleared') if self.translator else '更新配置文件已清空'}{Style.RESET_ALL}") 164 | except PermissionError: 165 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.yml_locked') if self.translator else '更新配置文件已被锁定,跳过清空'}{Style.RESET_ALL}") 166 | else: 167 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.update_yml_not_found') if self.translator else '更新配置文件不存在'}{Style.RESET_ALL}") 168 | return True 169 | 170 | except Exception as e: 171 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.clear_update_yml_failed', error=str(e)) if self.translator else f'清空更新配置文件失败: {e}'}{Style.RESET_ALL}") 172 | return False 173 | 174 | def _create_blocking_file(self): 175 | """Create blocking files""" 176 | try: 177 | # 检查 updater_path 178 | updater_path = self.updater_path 179 | if not updater_path: 180 | raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}") 181 | 182 | print(f"{Fore.CYAN}{EMOJI['FILE']} {self.translator.get('update.creating_block_file') if self.translator else '正在创建阻止文件...'}{Style.RESET_ALL}") 183 | 184 | # 创建 updater_path 阻止文件 185 | try: 186 | os.makedirs(os.path.dirname(updater_path), exist_ok=True) 187 | open(updater_path, 'w').close() 188 | 189 | # 设置 updater_path 为只读 190 | if self.system == "Windows": 191 | os.system(f'attrib +r "{updater_path}"') 192 | else: 193 | os.chmod(updater_path, 0o444) # 设置为只读 194 | 195 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.block_file_created') if self.translator else '阻止文件已创建'}: {updater_path}{Style.RESET_ALL}") 196 | except PermissionError: 197 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.block_file_locked') if self.translator else '阻止文件已被锁定,跳过创建'}{Style.RESET_ALL}") 198 | 199 | # 检查 update_yml_path 200 | update_yml_path = self.update_yml_path 201 | if update_yml_path and os.path.exists(os.path.dirname(update_yml_path)): 202 | try: 203 | # 创建 update_yml_path 阻止文件 204 | with open(update_yml_path, 'w') as f: 205 | f.write('# This file is locked to prevent auto-updates\nversion: 0.0.0\n') 206 | 207 | # 设置 update_yml_path 为只读 208 | if self.system == "Windows": 209 | os.system(f'attrib +r "{update_yml_path}"') 210 | else: 211 | os.chmod(update_yml_path, 0o444) # 设置为只读 212 | 213 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('update.yml_locked') if self.translator else '更新配置文件已锁定'}: {update_yml_path}{Style.RESET_ALL}") 214 | except PermissionError: 215 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('update.yml_already_locked') if self.translator else '更新配置文件已被锁定,跳过修改'}{Style.RESET_ALL}") 216 | 217 | return True 218 | 219 | except Exception as e: 220 | 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}") 221 | return True # 返回 True 以继续执行后续步骤 222 | 223 | def disable_auto_update(self): 224 | """Disable auto update""" 225 | try: 226 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('update.start_disable') if self.translator else '开始禁用自动更新...'}{Style.RESET_ALL}") 227 | 228 | # 1. End processes 229 | if not self._kill_cursor_processes(): 230 | return False 231 | 232 | # 2. Delete directory - 即使失败也继续执行 233 | self._remove_updater_directory() 234 | 235 | # 3. Clear update.yml file 236 | if not self._clear_update_yml_file(): 237 | return False 238 | 239 | # 4. Create blocking file 240 | if not self._create_blocking_file(): 241 | return False 242 | 243 | # 5. Remove update URL from product.json 244 | if not self._remove_update_url(): 245 | return False 246 | 247 | print(f"{Fore.GREEN}{EMOJI['CHECK']} {self.translator.get('update.disable_success') if self.translator else '自动更新已禁用'}{Style.RESET_ALL}") 248 | return True 249 | 250 | except Exception as e: 251 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('update.disable_failed', error=str(e)) if self.translator else f'禁用自动更新失败: {e}'}{Style.RESET_ALL}") 252 | return False 253 | 254 | def run(translator=None): 255 | """Convenient function for directly calling the disable function""" 256 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") 257 | print(f"{Fore.CYAN}{EMOJI['STOP']} {translator.get('update.title') if translator else 'Disable Cursor Auto Update'}{Style.RESET_ALL}") 258 | print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}") 259 | 260 | disabler = AutoUpdateDisabler(translator) 261 | disabler.disable_auto_update() 262 | 263 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") 264 | input(f"{EMOJI['INFO']} {translator.get('update.press_enter') if translator else 'Press Enter to Continue...'}") 265 | 266 | if __name__ == "__main__": 267 | from main import translator as main_translator 268 | run(main_translator) -------------------------------------------------------------------------------- /get_user_token.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import time 4 | from colorama import Fore, Style 5 | import os 6 | from config import get_config 7 | 8 | # Define emoji constants 9 | EMOJI = { 10 | 'START': '🚀', 11 | 'OAUTH': '🔑', 12 | 'SUCCESS': '✅', 13 | 'ERROR': '❌', 14 | 'WAIT': '⏳', 15 | 'INFO': 'ℹ️', 16 | 'WARNING': '⚠️' 17 | } 18 | 19 | def refresh_token(token, translator=None): 20 | """Refresh the token using the Chinese server API 21 | 22 | Args: 23 | token (str): The full WorkosCursorSessionToken cookie value 24 | translator: Optional translator object 25 | 26 | Returns: 27 | str: The refreshed access token or original token if refresh fails 28 | """ 29 | try: 30 | config = get_config(translator) 31 | # Get refresh_server URL from config or use default 32 | refresh_server = config.get('Token', 'refresh_server', fallback='https://token.cursorpro.com.cn') 33 | 34 | # Ensure the token is URL encoded properly 35 | if '%3A%3A' not in token and '::' in token: 36 | # Replace :: with URL encoded version if needed 37 | token = token.replace('::', '%3A%3A') 38 | 39 | # Make the request to the refresh server 40 | url = f"{refresh_server}/reftoken?token={token}" 41 | 42 | print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('token.refreshing') if translator else 'Refreshing token...'}{Style.RESET_ALL}") 43 | 44 | response = requests.get(url, timeout=30) 45 | 46 | if response.status_code == 200: 47 | try: 48 | data = response.json() 49 | 50 | if data.get('code') == 0 and data.get('msg') == "获取成功": 51 | access_token = data.get('data', {}).get('accessToken') 52 | days_left = data.get('data', {}).get('days_left', 0) 53 | expire_time = data.get('data', {}).get('expire_time', 'Unknown') 54 | 55 | if access_token: 56 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('token.refresh_success', days=days_left, expire=expire_time) if translator else f'Token refreshed successfully! Valid for {days_left} days (expires: {expire_time})'}{Style.RESET_ALL}") 57 | return access_token 58 | else: 59 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('token.no_access_token') if translator else 'No access token in response'}{Style.RESET_ALL}") 60 | else: 61 | error_msg = data.get('msg', 'Unknown error') 62 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.refresh_failed', error=error_msg) if translator else f'Token refresh failed: {error_msg}'}{Style.RESET_ALL}") 63 | except json.JSONDecodeError: 64 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.invalid_response') if translator else 'Invalid JSON response from refresh server'}{Style.RESET_ALL}") 65 | else: 66 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.server_error', status=response.status_code) if translator else f'Refresh server error: HTTP {response.status_code}'}{Style.RESET_ALL}") 67 | 68 | except requests.exceptions.Timeout: 69 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.request_timeout') if translator else 'Request to refresh server timed out'}{Style.RESET_ALL}") 70 | except requests.exceptions.ConnectionError: 71 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.connection_error') if translator else 'Connection error to refresh server'}{Style.RESET_ALL}") 72 | except Exception as e: 73 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.unexpected_error', error=str(e)) if translator else f'Unexpected error during token refresh: {str(e)}'}{Style.RESET_ALL}") 74 | 75 | # Return original token if refresh fails 76 | return token.split('%3A%3A')[-1] if '%3A%3A' in token else token.split('::')[-1] if '::' in token else token 77 | 78 | def get_token_from_cookie(cookie_value, translator=None): 79 | """Extract and process token from cookie value 80 | 81 | Args: 82 | cookie_value (str): The WorkosCursorSessionToken cookie value 83 | translator: Optional translator object 84 | 85 | Returns: 86 | str: The processed token 87 | """ 88 | try: 89 | # Try to refresh the token with the API first 90 | refreshed_token = refresh_token(cookie_value, translator) 91 | 92 | # If refresh succeeded and returned a different token, use it 93 | if refreshed_token and refreshed_token != cookie_value: 94 | return refreshed_token 95 | 96 | # If refresh failed or returned same token, use traditional extraction method 97 | if '%3A%3A' in cookie_value: 98 | return cookie_value.split('%3A%3A')[-1] 99 | elif '::' in cookie_value: 100 | return cookie_value.split('::')[-1] 101 | else: 102 | return cookie_value 103 | 104 | except Exception as e: 105 | print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.extraction_error', error=str(e)) if translator else f'Error extracting token: {str(e)}'}{Style.RESET_ALL}") 106 | # Fall back to original behavior 107 | if '%3A%3A' in cookie_value: 108 | return cookie_value.split('%3A%3A')[-1] 109 | elif '::' in cookie_value: 110 | return cookie_value.split('::')[-1] 111 | else: 112 | return cookie_value -------------------------------------------------------------------------------- /images/cloudflare_2025-02-12_13-43-21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/cloudflare_2025-02-12_13-43-21.png -------------------------------------------------------------------------------- /images/fix_2025-01-14_21-30-43.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/fix_2025-01-14_21-30-43.png -------------------------------------------------------------------------------- /images/free_2025-01-14_14-59-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/free_2025-01-14_14-59-15.png -------------------------------------------------------------------------------- /images/locale_2025-01-15_13-40-08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/locale_2025-01-15_13-40-08.png -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/logo.png -------------------------------------------------------------------------------- /images/new107_2025-01-15_13-53-56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/new107_2025-01-15_13-53-56.png -------------------------------------------------------------------------------- /images/new_2025-02-27_10-42-44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/new_2025-02-27_10-42-44.png -------------------------------------------------------------------------------- /images/new_2025-03-19_00-19-09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/new_2025-03-19_00-19-09.png -------------------------------------------------------------------------------- /images/new_2025-03-22_19-53-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/new_2025-03-22_19-53-10.png -------------------------------------------------------------------------------- /images/pass_2025-02-08_21-48-36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pass_2025-02-08_21-48-36.png -------------------------------------------------------------------------------- /images/paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/paypal.png -------------------------------------------------------------------------------- /images/pro_2025-01-11_00-50-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pro_2025-01-11_00-50-40.png -------------------------------------------------------------------------------- /images/pro_2025-01-11_00-51-07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pro_2025-01-11_00-51-07.png -------------------------------------------------------------------------------- /images/pro_2025-01-11_16-24-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pro_2025-01-11_16-24-03.png -------------------------------------------------------------------------------- /images/pro_2025-01-11_22-33-09.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pro_2025-01-11_22-33-09.gif -------------------------------------------------------------------------------- /images/pro_2025-01-13_13-49-55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pro_2025-01-13_13-49-55.png -------------------------------------------------------------------------------- /images/pro_2025-01-14_14-40-37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pro_2025-01-14_14-40-37.png -------------------------------------------------------------------------------- /images/pro_2025-04-05_18-47-56.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pro_2025-04-05_18-47-56.png -------------------------------------------------------------------------------- /images/product_2025-04-16_10-40-21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/product_2025-04-16_10-40-21.png -------------------------------------------------------------------------------- /images/pronew_2025-02-13_15-01-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/pronew_2025-02-13_15-01-32.png -------------------------------------------------------------------------------- /images/provi-code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/provi-code.jpg -------------------------------------------------------------------------------- /images/what_2025-01-13_13-32-54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yeongpin/cursor-free-vip/4286b94a868b7fb1205317094750f3a0b1a8bdff/images/what_2025-01-13_13-32-54.png -------------------------------------------------------------------------------- /locales/it.json: -------------------------------------------------------------------------------- 1 | { 2 | "menu": { 3 | "title": "Opzioni Disponibili", 4 | "exit": "Esci dal Programma", 5 | "reset": "Reimposta ID Macchina", 6 | "register": "Registra Nuovo Account Cursor", 7 | "register_google": "Registrati con il Tuo Account Google", 8 | "register_github": "Registrati con il Tuo Account GitHub", 9 | "register_manual": "Registra Cursor con Email Personalizzata", 10 | "quit": "Chiudi Applicazione Cursor", 11 | "select_language": "Cambia Lingua", 12 | "select_chrome_profile": "Seleziona Profilo Chrome", 13 | "input_choice": "Inserisci la tua scelta ({choices})", 14 | "invalid_choice": "Selezione non valida. Inserisci un numero da {choices}", 15 | "program_terminated": "Programma terminato dall'utente", 16 | "error_occurred": "Si è verificato un errore: {error}. Riprova", 17 | "press_enter": "Premi Invio per Uscire", 18 | "disable_auto_update": "Disabilita Aggiornamento Automatico di Cursor", 19 | "lifetime_access_enabled": "ACCESSO A VITA ABILITATO", 20 | "totally_reset": "Reimposta Completamente Cursor", 21 | "outdate": "Obsoleto", 22 | "temp_github_register": "Registrazione GitHub Temporanea", 23 | "admin_required": "Esecuzione come file eseguibile, richiesti privilegi di amministratore.", 24 | "admin_required_continue": "Continua senza privilegi di amministratore.", 25 | "coming_soon": "Prossimamente", 26 | "fixed_soon": "Corretto Presto", 27 | "contribute": "Contribuisci al Progetto", 28 | "config": "Mostra Configurazione", 29 | "delete_google_account": "Elimina Account Google di Cursor", 30 | "continue_prompt": "Continuare? (y/N): ", 31 | "operation_cancelled_by_user": "Operazione annullata dall'utente", 32 | "exiting": "Uscita in corso...", 33 | "bypass_version_check": "Ignora Controllo Versione Cursor", 34 | "check_user_authorized": "Verifica Autorizzazione Utente", 35 | "bypass_token_limit": "Ignora Limite Token", 36 | "language_config_saved": "Configurazione lingua salvata con successo", 37 | "lang_invalid_choice": "Scelta non valida. Inserisci una delle seguenti opzioni: ({lang_choices})", 38 | "restore_machine_id": "Ripristina ID Macchina dal Backup" 39 | }, 40 | "languages": { 41 | "ar": "Arabo", 42 | "en": "Inglese", 43 | "zh_cn": "Cinese Semplificato", 44 | "zh_tw": "Cinese Tradizionale", 45 | "vi": "Vietnamita", 46 | "nl": "Olandese", 47 | "de": "Tedesco", 48 | "fr": "Francese", 49 | "pt": "Portoghese", 50 | "ru": "Russo", 51 | "tr": "Turco", 52 | "bg": "Bulgaro", 53 | "es": "Spagnolo", 54 | "ja": "Giapponese", 55 | "it": "Italiano" 56 | } 57 | } -------------------------------------------------------------------------------- /locales/ja.json: -------------------------------------------------------------------------------- 1 | { 2 | "menu": { 3 | "title": "利用可能なオプション", 4 | "exit": "プログラムを終了", 5 | "reset": "マシンIDをリセット", 6 | "register": "新しいCursorアカウントを登録", 7 | "register_google": "Googleアカウントで登録", 8 | "register_github": "GitHubアカウントで登録", 9 | "register_manual": "カスタムメールでCursorを登録", 10 | "quit": "Cursorアプリケーションを閉じる", 11 | "select_language": "言語を変更", 12 | "select_chrome_profile": "Chromeプロファイルを選択", 13 | "input_choice": "選択肢を入力してください ({choices})", 14 | "invalid_choice": "無効な選択です。{choices}から数字を入力してください", 15 | "program_terminated": "プログラムはユーザーによって終了されました", 16 | "error_occurred": "エラーが発生しました: {error}。もう一度お試しください", 17 | "press_enter": "終了するにはEnterキーを押してください", 18 | "disable_auto_update": "Cursorの自動更新を無効化", 19 | "lifetime_access_enabled": "ライフタイムアクセスが有効化されました", 20 | "totally_reset": "Cursorを完全にリセット", 21 | "outdate": "期限切れ", 22 | "temp_github_register": "一時的なGitHub登録", 23 | "admin_required": "実行ファイルとして実行中、管理者権限が必要です。", 24 | "admin_required_continue": "管理者権限なしで続行します。", 25 | "coming_soon": "近日公開", 26 | "fixed_soon": "近日修正予定", 27 | "contribute": "プロジェクトに貢献", 28 | "config": "設定を表示", 29 | "delete_google_account": "CursorのGoogleアカウントを削除", 30 | "continue_prompt": "続行しますか?(y/N): ", 31 | "operation_cancelled_by_user": "操作はユーザーによってキャンセルされました", 32 | "exiting": "終了中……", 33 | "bypass_version_check": "Cursorのバージョンチェックをバイパス", 34 | "check_user_authorized": "ユーザーの認証を確認", 35 | "bypass_token_limit": "トークン制限をバイパス", 36 | "language_config_saved": "言語設定が正常に保存されました", 37 | "lang_invalid_choice": "無効な選択です。以下のオプションから選択してください: ({lang_choices})", 38 | "restore_machine_id": "バックアップからマシンIDを復元" 39 | }, 40 | "languages": { 41 | "ar": "アラビア語", 42 | "en": "英語", 43 | "zh_cn": "簡体字中国語", 44 | "zh_tw": "繁体字中国語", 45 | "vi": "ベトナム語", 46 | "nl": "オランダ語", 47 | "de": "ドイツ語", 48 | "fr": "フランス語", 49 | "pt": "ポルトガル語", 50 | "ru": "ロシア語", 51 | "tr": "トルコ語", 52 | "bg": "ブルガリア語", 53 | "es": "スペイン語", 54 | "ja": "日本語", 55 | "it": "イタリア語" 56 | } 57 | } -------------------------------------------------------------------------------- /logo.py: -------------------------------------------------------------------------------- 1 | from colorama import Fore, Style, init 2 | from dotenv import load_dotenv 3 | import os 4 | import shutil 5 | import re 6 | 7 | # Get the current script directory 8 | current_dir = os.path.dirname(os.path.abspath(__file__)) 9 | # Build the full path to the .env file 10 | env_path = os.path.join(current_dir, '.env') 11 | 12 | # Load environment variables, specifying the .env file path 13 | load_dotenv(env_path) 14 | # Get the version number, using the default value if not found 15 | version = os.getenv('VERSION', '1.0.0') 16 | 17 | # Initialize colorama 18 | init() 19 | 20 | # get terminal width 21 | def get_terminal_width(): 22 | try: 23 | columns, _ = shutil.get_terminal_size()/2 24 | return columns 25 | except: 26 | return 80 # default width 27 | 28 | # center display text (not handling Chinese characters) 29 | def center_multiline_text(text, handle_chinese=False): 30 | width = get_terminal_width() 31 | lines = text.split('\n') 32 | centered_lines = [] 33 | 34 | for line in lines: 35 | # calculate actual display width (remove ANSI color codes) 36 | clean_line = line 37 | for color in [Fore.CYAN, Fore.YELLOW, Fore.GREEN, Fore.RED, Fore.BLUE, Style.RESET_ALL]: 38 | clean_line = clean_line.replace(color, '') 39 | 40 | # remove all ANSI escape sequences to get the actual length 41 | ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') 42 | clean_line = ansi_escape.sub('', clean_line) 43 | 44 | # calculate display width 45 | if handle_chinese: 46 | # consider Chinese characters occupying two positions 47 | display_width = 0 48 | for char in clean_line: 49 | if ord(char) > 127: # non-ASCII characters 50 | display_width += 2 51 | else: 52 | display_width += 1 53 | else: 54 | # not handling Chinese characters 55 | display_width = len(clean_line) 56 | 57 | # calculate the number of spaces to add 58 | padding = max(0, (width - display_width) // 2) 59 | centered_lines.append(' ' * padding + line) 60 | 61 | return '\n'.join(centered_lines) 62 | 63 | # original LOGO text 64 | LOGO_TEXT = f"""{Fore.CYAN} 65 | ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ 66 | ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ ██╔══██╗██╔══██╗██╔═══██╗ 67 | ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ ██████╔╝██████╔╝██║ ██║ 68 | ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ ██╔═══╝ ██╔══██╗██║ ██║ 69 | ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ ██║ ██║ ██║╚██████╔╝ 70 | ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ 71 | {Style.RESET_ALL}""" 72 | 73 | DESCRIPTION_TEXT = f"""{Fore.YELLOW} 74 | Pro Version Activator v{version}{Fore.GREEN} 75 | Author: Pin Studios (yeongpin)""" 76 | 77 | CONTRIBUTORS_TEXT = f"""{Fore.BLUE} 78 | Contributors: 79 | BasaiCorp aliensb handwerk2016 Nigel1992 80 | UntaDotMy RenjiYuusei imbajin ahmed98Osama 81 | bingoohuang mALIk-sHAHId MFaiqKhan httpmerak 82 | muhammedfurkan plamkatawe Lucaszmv 83 | """ 84 | OTHER_INFO_TEXT = f"""{Fore.YELLOW} 85 | Github: https://github.com/yeongpin/cursor-free-vip{Fore.RED} 86 | Press 4 to change language | 按下 4 键切换语言{Style.RESET_ALL}""" 87 | 88 | # center display LOGO and DESCRIPTION 89 | CURSOR_LOGO = center_multiline_text(LOGO_TEXT, handle_chinese=False) 90 | CURSOR_DESCRIPTION = center_multiline_text(DESCRIPTION_TEXT, handle_chinese=False) 91 | CURSOR_CONTRIBUTORS = center_multiline_text(CONTRIBUTORS_TEXT, handle_chinese=False) 92 | CURSOR_OTHER_INFO = center_multiline_text(OTHER_INFO_TEXT, handle_chinese=True) 93 | 94 | def print_logo(): 95 | print(CURSOR_LOGO) 96 | print(CURSOR_DESCRIPTION) 97 | # print(CURSOR_CONTRIBUTORS) 98 | print(CURSOR_OTHER_INFO) 99 | 100 | if __name__ == "__main__": 101 | print_logo() 102 | -------------------------------------------------------------------------------- /quit_cursor.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | import time 3 | from colorama import Fore, Style, init 4 | import sys 5 | import os 6 | 7 | # Initialize colorama 8 | init() 9 | 10 | # Define emoji constants 11 | EMOJI = { 12 | "PROCESS": "⚙️", 13 | "SUCCESS": "✅", 14 | "ERROR": "❌", 15 | "INFO": "ℹ️", 16 | "WAIT": "⏳" 17 | } 18 | 19 | class CursorQuitter: 20 | def __init__(self, timeout=5, translator=None): 21 | self.timeout = timeout 22 | self.translator = translator # Use the passed translator 23 | 24 | def quit_cursor(self): 25 | """Gently close Cursor processes""" 26 | try: 27 | print(f"{Fore.CYAN}{EMOJI['PROCESS']} {self.translator.get('quit_cursor.start')}...{Style.RESET_ALL}") 28 | cursor_processes = [] 29 | 30 | # Collect all Cursor processes 31 | for proc in psutil.process_iter(['pid', 'name']): 32 | try: 33 | if proc.info['name'].lower() in ['cursor.exe', 'cursor']: 34 | cursor_processes.append(proc) 35 | except (psutil.NoSuchProcess, psutil.AccessDenied): 36 | continue 37 | 38 | if not cursor_processes: 39 | print(f"{Fore.GREEN}{EMOJI['INFO']} {self.translator.get('quit_cursor.no_process')}{Style.RESET_ALL}") 40 | return True 41 | 42 | # Gently request processes to terminate 43 | for proc in cursor_processes: 44 | try: 45 | if proc.is_running(): 46 | print(f"{Fore.YELLOW}{EMOJI['PROCESS']} {self.translator.get('quit_cursor.terminating', pid=proc.pid)}...{Style.RESET_ALL}") 47 | proc.terminate() 48 | except (psutil.NoSuchProcess, psutil.AccessDenied): 49 | continue 50 | 51 | # Wait for processes to terminate naturally 52 | print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('quit_cursor.waiting')}...{Style.RESET_ALL}") 53 | start_time = time.time() 54 | while time.time() - start_time < self.timeout: 55 | still_running = [] 56 | for proc in cursor_processes: 57 | try: 58 | if proc.is_running(): 59 | still_running.append(proc) 60 | except (psutil.NoSuchProcess, psutil.AccessDenied): 61 | continue 62 | 63 | if not still_running: 64 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('quit_cursor.success')}{Style.RESET_ALL}") 65 | return True 66 | 67 | time.sleep(0.5) 68 | 69 | # If processes are still running after timeout 70 | if still_running: 71 | process_list = ", ".join([str(p.pid) for p in still_running]) 72 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('quit_cursor.timeout', pids=process_list)}{Style.RESET_ALL}") 73 | return False 74 | 75 | return True 76 | 77 | except Exception as e: 78 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('quit_cursor.error', error=str(e))}{Style.RESET_ALL}") 79 | return False 80 | 81 | def quit_cursor(translator=None, timeout=5): 82 | """Convenient function for directly calling the quit function""" 83 | quitter = CursorQuitter(timeout, translator) 84 | return quitter.quit_cursor() 85 | 86 | if __name__ == "__main__": 87 | # If run directly, use the default translator 88 | from main import translator as main_translator 89 | quit_cursor(main_translator) -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | watchdog 2 | python-dotenv>=1.0.0 3 | colorama>=0.4.6 4 | requests 5 | psutil>=5.8.0 6 | pywin32; platform_system == "Windows" 7 | pyinstaller 8 | DrissionPage>=4.0.0 9 | selenium 10 | webdriver_manager 11 | arabic-reshaper 12 | python-bidi 13 | faker -------------------------------------------------------------------------------- /restore_machine_id.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import uuid 5 | import hashlib 6 | import shutil 7 | import sqlite3 8 | import platform 9 | import re 10 | import glob 11 | import tempfile 12 | from colorama import Fore, Style, init 13 | from typing import Tuple 14 | import configparser 15 | import traceback 16 | from config import get_config 17 | from datetime import datetime 18 | 19 | # 导入共享函数 20 | from reset_machine_manual import get_cursor_machine_id_path, get_user_documents_path 21 | 22 | # 初始化 colorama 23 | init() 24 | 25 | # 定义表情符号常量 26 | EMOJI = { 27 | "FILE": "📄", 28 | "BACKUP": "💾", 29 | "SUCCESS": "✅", 30 | "ERROR": "❌", 31 | "INFO": "ℹ️", 32 | "RESET": "🔄", 33 | "WARNING": "⚠️", 34 | } 35 | 36 | class ConfigError(Exception): 37 | """配置错误异常""" 38 | pass 39 | 40 | class MachineIDRestorer: 41 | def __init__(self, translator=None): 42 | self.translator = translator 43 | 44 | # 读取配置 45 | config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip") 46 | config_file = os.path.join(config_dir, "config.ini") 47 | config = configparser.ConfigParser() 48 | 49 | if not os.path.exists(config_file): 50 | raise FileNotFoundError(f"Config file not found: {config_file}") 51 | 52 | config.read(config_file, encoding='utf-8') 53 | 54 | # 根据操作系统获取路径 55 | if sys.platform == "win32": # Windows 56 | appdata = os.getenv("APPDATA") 57 | if appdata is None: 58 | raise EnvironmentError("APPDATA Environment Variable Not Set") 59 | 60 | if not config.has_section('WindowsPaths'): 61 | raise ConfigError("WindowsPaths section not found in config") 62 | 63 | self.db_path = config.get('WindowsPaths', 'storage_path') 64 | self.sqlite_path = config.get('WindowsPaths', 'sqlite_path') 65 | 66 | elif sys.platform == "darwin": # macOS 67 | if not config.has_section('MacPaths'): 68 | raise ConfigError("MacPaths section not found in config") 69 | 70 | self.db_path = config.get('MacPaths', 'storage_path') 71 | self.sqlite_path = config.get('MacPaths', 'sqlite_path') 72 | 73 | elif sys.platform == "linux": # Linux 74 | if not config.has_section('LinuxPaths'): 75 | raise ConfigError("LinuxPaths section not found in config") 76 | 77 | self.db_path = config.get('LinuxPaths', 'storage_path') 78 | self.sqlite_path = config.get('LinuxPaths', 'sqlite_path') 79 | 80 | else: 81 | raise NotImplementedError(f"Not Supported OS: {sys.platform}") 82 | 83 | def find_backups(self): 84 | """查找可用的备份文件""" 85 | db_dir = os.path.dirname(self.db_path) 86 | db_name = os.path.basename(self.db_path) 87 | 88 | # 查找格式为 {db_name}.bak.{timestamp} 的文件 89 | backup_pattern = f"{db_name}.bak.*" 90 | backups = glob.glob(os.path.join(db_dir, backup_pattern)) 91 | 92 | # 按创建时间排序(最新的在前) 93 | backups.sort(key=os.path.getctime, reverse=True) 94 | 95 | return backups 96 | 97 | def list_backups(self): 98 | """列出所有可用备份""" 99 | backups = self.find_backups() 100 | 101 | if not backups: 102 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('restore.no_backups_found')}{Style.RESET_ALL}") 103 | return None 104 | 105 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('restore.available_backups')}:{Style.RESET_ALL}") 106 | for i, backup in enumerate(backups, 1): 107 | # 获取备份文件信息 108 | timestamp_str = backup.split('.')[-1] 109 | try: 110 | # 尝试解析时间戳(如果格式为 YYYYmmdd_HHMMSS) 111 | timestamp = datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S") 112 | date_str = timestamp.strftime("%Y-%m-%d %H:%M:%S") 113 | except ValueError: 114 | date_str = "未知日期" 115 | 116 | # 获取文件大小 117 | size = os.path.getsize(backup) 118 | size_str = f"{size / 1024:.1f} KB" 119 | 120 | print(f"{i}. {Fore.GREEN}{os.path.basename(backup)}{Style.RESET_ALL} ({date_str}, {size_str})") 121 | 122 | return backups 123 | 124 | def select_backup(self): 125 | """让用户选择要恢复的备份""" 126 | backups = self.list_backups() 127 | 128 | if not backups: 129 | return None 130 | 131 | while True: 132 | try: 133 | choice = input(f"{EMOJI['INFO']} {self.translator.get('restore.select_backup')} (1-{len(backups)}, 0 {self.translator.get('restore.to_cancel')}): ") 134 | 135 | if choice.strip() == '0': 136 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('restore.operation_cancelled')}{Style.RESET_ALL}") 137 | return None 138 | 139 | index = int(choice) - 1 140 | if 0 <= index < len(backups): 141 | return backups[index] 142 | else: 143 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.invalid_selection')}{Style.RESET_ALL}") 144 | except ValueError: 145 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.please_enter_number')}{Style.RESET_ALL}") 146 | 147 | def extract_ids_from_backup(self, backup_path): 148 | """从备份文件中提取机器ID""" 149 | try: 150 | with open(backup_path, "r", encoding="utf-8") as f: 151 | backup_data = json.load(f) 152 | 153 | # 提取需要恢复的ID 154 | ids = { 155 | "telemetry.devDeviceId": backup_data.get("telemetry.devDeviceId", ""), 156 | "telemetry.macMachineId": backup_data.get("telemetry.macMachineId", ""), 157 | "telemetry.machineId": backup_data.get("telemetry.machineId", ""), 158 | "telemetry.sqmId": backup_data.get("telemetry.sqmId", ""), 159 | "storage.serviceMachineId": backup_data.get("storage.serviceMachineId", 160 | backup_data.get("telemetry.devDeviceId", "")) 161 | } 162 | 163 | # 确保所有ID都存在 164 | for key, value in ids.items(): 165 | if not value: 166 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('restore.missing_id', id=key)}{Style.RESET_ALL}") 167 | 168 | return ids 169 | except Exception as e: 170 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.read_backup_failed', error=str(e))}{Style.RESET_ALL}") 171 | return None 172 | 173 | def update_current_file(self, ids): 174 | """更新当前的storage.json文件""" 175 | try: 176 | if not os.path.exists(self.db_path): 177 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.current_file_not_found')}: {self.db_path}{Style.RESET_ALL}") 178 | return False 179 | 180 | # 读取当前文件 181 | with open(self.db_path, "r", encoding="utf-8") as f: 182 | current_data = json.load(f) 183 | 184 | # 创建当前文件的备份 185 | timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") 186 | backup_path = f"{self.db_path}.restore_bak.{timestamp}" 187 | shutil.copy2(self.db_path, backup_path) 188 | print(f"{Fore.GREEN}{EMOJI['BACKUP']} {self.translator.get('restore.current_backup_created')}: {backup_path}{Style.RESET_ALL}") 189 | 190 | # 更新ID 191 | current_data.update(ids) 192 | 193 | # 保存更新后的文件 194 | with open(self.db_path, "w", encoding="utf-8") as f: 195 | json.dump(current_data, f, indent=4) 196 | 197 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('restore.storage_updated')}{Style.RESET_ALL}") 198 | return True 199 | except Exception as e: 200 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.update_failed', error=str(e))}{Style.RESET_ALL}") 201 | return False 202 | 203 | def update_sqlite_db(self, ids): 204 | """更新SQLite数据库中的ID""" 205 | try: 206 | if not os.path.exists(self.sqlite_path): 207 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.sqlite_not_found')}: {self.sqlite_path}{Style.RESET_ALL}") 208 | return False 209 | 210 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('restore.updating_sqlite')}...{Style.RESET_ALL}") 211 | 212 | conn = sqlite3.connect(self.sqlite_path) 213 | cursor = conn.cursor() 214 | 215 | cursor.execute(""" 216 | CREATE TABLE IF NOT EXISTS ItemTable ( 217 | key TEXT PRIMARY KEY, 218 | value TEXT 219 | ) 220 | """) 221 | 222 | for key, value in ids.items(): 223 | cursor.execute(""" 224 | INSERT OR REPLACE INTO ItemTable (key, value) 225 | VALUES (?, ?) 226 | """, (key, value)) 227 | print(f"{EMOJI['INFO']} {Fore.CYAN} {self.translator.get('restore.updating_pair')}: {key}{Style.RESET_ALL}") 228 | 229 | conn.commit() 230 | conn.close() 231 | 232 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('restore.sqlite_updated')}{Style.RESET_ALL}") 233 | return True 234 | except Exception as e: 235 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.sqlite_update_failed', error=str(e))}{Style.RESET_ALL}") 236 | return False 237 | 238 | def update_machine_id_file(self, dev_device_id): 239 | """更新machineId文件""" 240 | try: 241 | machine_id_path = get_cursor_machine_id_path(self.translator) 242 | 243 | # 创建目录(如果不存在) 244 | os.makedirs(os.path.dirname(machine_id_path), exist_ok=True) 245 | 246 | # 备份当前文件(如果存在) 247 | if os.path.exists(machine_id_path): 248 | timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") 249 | backup_path = f"{machine_id_path}.restore_bak.{timestamp}" 250 | try: 251 | shutil.copy2(machine_id_path, backup_path) 252 | print(f"{Fore.GREEN}{EMOJI['INFO']} {self.translator.get('restore.machine_id_backup_created')}: {backup_path}{Style.RESET_ALL}") 253 | except Exception as e: 254 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('restore.backup_creation_failed', error=str(e))}{Style.RESET_ALL}") 255 | 256 | # 写入新的ID 257 | with open(machine_id_path, "w", encoding="utf-8") as f: 258 | f.write(dev_device_id) 259 | 260 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('restore.machine_id_updated')}{Style.RESET_ALL}") 261 | return True 262 | except Exception as e: 263 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.machine_id_update_failed', error=str(e))}{Style.RESET_ALL}") 264 | return False 265 | 266 | def update_system_ids(self, ids): 267 | """更新系统级ID(特定于操作系统)""" 268 | try: 269 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('restore.updating_system_ids')}...{Style.RESET_ALL}") 270 | 271 | if sys.platform.startswith("win"): 272 | self._update_windows_system_ids(ids) 273 | elif sys.platform == "darwin": 274 | self._update_macos_system_ids(ids) 275 | 276 | return True 277 | except Exception as e: 278 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.system_ids_update_failed', error=str(e))}{Style.RESET_ALL}") 279 | return False 280 | 281 | def _update_windows_system_ids(self, ids): 282 | """更新Windows系统ID""" 283 | try: 284 | import winreg 285 | 286 | # 更新MachineGuid 287 | guid = ids.get("telemetry.devDeviceId", "") 288 | if guid: 289 | try: 290 | key = winreg.OpenKey( 291 | winreg.HKEY_LOCAL_MACHINE, 292 | "SOFTWARE\\Microsoft\\Cryptography", 293 | 0, 294 | winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY 295 | ) 296 | winreg.SetValueEx(key, "MachineGuid", 0, winreg.REG_SZ, guid) 297 | winreg.CloseKey(key) 298 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('restore.windows_machine_guid_updated')}{Style.RESET_ALL}") 299 | except PermissionError: 300 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.permission_denied')}{Style.RESET_ALL}") 301 | except Exception as e: 302 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.update_windows_machine_guid_failed', error=str(e))}{Style.RESET_ALL}") 303 | 304 | # 更新SQMClient MachineId 305 | sqm_id = ids.get("telemetry.sqmId", "") 306 | if sqm_id: 307 | try: 308 | key = winreg.OpenKey( 309 | winreg.HKEY_LOCAL_MACHINE, 310 | r"SOFTWARE\Microsoft\SQMClient", 311 | 0, 312 | winreg.KEY_WRITE | winreg.KEY_WOW64_64KEY 313 | ) 314 | winreg.SetValueEx(key, "MachineId", 0, winreg.REG_SZ, sqm_id) 315 | winreg.CloseKey(key) 316 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('restore.windows_machine_id_updated')}{Style.RESET_ALL}") 317 | except FileNotFoundError: 318 | print(f"{Fore.YELLOW}{EMOJI['WARNING']} {self.translator.get('restore.sqm_client_key_not_found')}{Style.RESET_ALL}") 319 | except PermissionError: 320 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.permission_denied')}{Style.RESET_ALL}") 321 | except Exception as e: 322 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.update_windows_machine_id_failed', error=str(e))}{Style.RESET_ALL}") 323 | except Exception as e: 324 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.update_windows_system_ids_failed', error=str(e))}{Style.RESET_ALL}") 325 | 326 | def _update_macos_system_ids(self, ids): 327 | """更新macOS系统ID""" 328 | try: 329 | uuid_file = "/var/root/Library/Preferences/SystemConfiguration/com.apple.platform.uuid.plist" 330 | if os.path.exists(uuid_file): 331 | mac_id = ids.get("telemetry.macMachineId", "") 332 | if mac_id: 333 | cmd = f'sudo plutil -replace "UUID" -string "{mac_id}" "{uuid_file}"' 334 | result = os.system(cmd) 335 | if result == 0: 336 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('restore.macos_platform_uuid_updated')}{Style.RESET_ALL}") 337 | else: 338 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.failed_to_execute_plutil_command')}{Style.RESET_ALL}") 339 | except Exception as e: 340 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.update_macos_system_ids_failed', error=str(e))}{Style.RESET_ALL}") 341 | 342 | def restore_machine_ids(self): 343 | """恢复之前备份的机器ID""" 344 | try: 345 | print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('restore.starting')}...{Style.RESET_ALL}") 346 | 347 | # 选择要恢复的备份 348 | backup_path = self.select_backup() 349 | if not backup_path: 350 | return False 351 | 352 | # 从备份中提取ID 353 | ids = self.extract_ids_from_backup(backup_path) 354 | if not ids: 355 | return False 356 | 357 | # 显示将要恢复的ID 358 | print(f"\n{Fore.CYAN}{self.translator.get('restore.ids_to_restore')}:{Style.RESET_ALL}") 359 | for key, value in ids.items(): 360 | print(f"{EMOJI['INFO']} {key}: {Fore.GREEN}{value}{Style.RESET_ALL}") 361 | 362 | # 确认恢复 363 | confirm = input(f"\n{EMOJI['WARNING']} {self.translator.get('restore.confirm')} (y/n): ") 364 | if confirm.lower() != 'y': 365 | print(f"{Fore.YELLOW}{EMOJI['INFO']} {self.translator.get('restore.operation_cancelled')}{Style.RESET_ALL}") 366 | return False 367 | 368 | # 更新当前文件 369 | if not self.update_current_file(ids): 370 | return False 371 | 372 | # 更新SQLite数据库 373 | self.update_sqlite_db(ids) 374 | 375 | # 更新machineId文件 376 | self.update_machine_id_file(ids.get("telemetry.devDeviceId", "")) 377 | 378 | # 更新系统ID 379 | self.update_system_ids(ids) 380 | 381 | print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('restore.success')}{Style.RESET_ALL}") 382 | return True 383 | 384 | except Exception as e: 385 | print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('restore.process_error', error=str(e))}{Style.RESET_ALL}") 386 | return False 387 | 388 | def run(translator=None): 389 | """恢复机器ID的主函数""" 390 | config = get_config(translator) 391 | if not config: 392 | return False 393 | 394 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") 395 | print(f"{Fore.CYAN}{EMOJI['RESET']} {translator.get('restore.title')}{Style.RESET_ALL}") 396 | print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}") 397 | 398 | restorer = MachineIDRestorer(translator) 399 | restorer.restore_machine_ids() 400 | 401 | print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") 402 | input(f"{EMOJI['INFO']} {translator.get('restore.press_enter')}...") 403 | 404 | if __name__ == "__main__": 405 | from main import translator as main_translator 406 | run(main_translator) -------------------------------------------------------------------------------- /scripts/install.ps1: -------------------------------------------------------------------------------- 1 | # set color theme 2 | $Theme = @{ 3 | Primary = 'Cyan' 4 | Success = 'Green' 5 | Warning = 'Yellow' 6 | Error = 'Red' 7 | Info = 'White' 8 | } 9 | 10 | # ASCII Logo 11 | $Logo = @" 12 | ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ 13 | ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ ██╔══██╗██╔══██╗██╔═══██╗ 14 | ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ ██████╔╝██████╔╝██║ ██║ 15 | ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ ██╔═══╝ ██╔══██╗██║ ██║ 16 | ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ ██║ ██║ ██║╚██████╔╝ 17 | ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ 18 | "@ 19 | 20 | # Beautiful Output Function 21 | function Write-Styled { 22 | param ( 23 | [string]$Message, 24 | [string]$Color = $Theme.Info, 25 | [string]$Prefix = "", 26 | [switch]$NoNewline 27 | ) 28 | $symbol = switch ($Color) { 29 | $Theme.Success { "[OK]" } 30 | $Theme.Error { "[X]" } 31 | $Theme.Warning { "[!]" } 32 | default { "[*]" } 33 | } 34 | 35 | $output = if ($Prefix) { "$symbol $Prefix :: $Message" } else { "$symbol $Message" } 36 | if ($NoNewline) { 37 | Write-Host $output -ForegroundColor $Color -NoNewline 38 | } else { 39 | Write-Host $output -ForegroundColor $Color 40 | } 41 | } 42 | 43 | # Get version number function 44 | function Get-LatestVersion { 45 | try { 46 | $latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/yeongpin/cursor-free-vip/releases/latest" 47 | return @{ 48 | Version = $latestRelease.tag_name.TrimStart('v') 49 | Assets = $latestRelease.assets 50 | } 51 | } catch { 52 | Write-Styled $_.Exception.Message -Color $Theme.Error -Prefix "Error" 53 | throw "Cannot get latest version" 54 | } 55 | } 56 | 57 | # Show Logo 58 | Write-Host $Logo -ForegroundColor $Theme.Primary 59 | $releaseInfo = Get-LatestVersion 60 | $version = $releaseInfo.Version 61 | Write-Host "Version $version" -ForegroundColor $Theme.Info 62 | Write-Host "Created by YeongPin`n" -ForegroundColor $Theme.Info 63 | 64 | # Set TLS 1.2 65 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 66 | 67 | # Main installation function 68 | function Install-CursorFreeVIP { 69 | Write-Styled "Start downloading Cursor Free VIP" -Color $Theme.Primary -Prefix "Download" 70 | 71 | try { 72 | # Get latest version 73 | Write-Styled "Checking latest version..." -Color $Theme.Primary -Prefix "Update" 74 | $releaseInfo = Get-LatestVersion 75 | $version = $releaseInfo.Version 76 | Write-Styled "Found latest version: $version" -Color $Theme.Success -Prefix "Version" 77 | 78 | # Find corresponding resources 79 | $asset = $releaseInfo.Assets | Where-Object { $_.name -eq "CursorFreeVIP_${version}_windows.exe" } 80 | if (!$asset) { 81 | Write-Styled "File not found: CursorFreeVIP_${version}_windows.exe" -Color $Theme.Error -Prefix "Error" 82 | Write-Styled "Available files:" -Color $Theme.Warning -Prefix "Info" 83 | $releaseInfo.Assets | ForEach-Object { 84 | Write-Styled "- $($_.name)" -Color $Theme.Info 85 | } 86 | throw "Cannot find target file" 87 | } 88 | 89 | # Check if Downloads folder already exists for the corresponding version 90 | $DownloadsPath = [Environment]::GetFolderPath("UserProfile") + "\Downloads" 91 | $downloadPath = Join-Path $DownloadsPath "CursorFreeVIP_${version}_windows.exe" 92 | 93 | if (Test-Path $downloadPath) { 94 | Write-Styled "Found existing installation file" -Color $Theme.Success -Prefix "Found" 95 | Write-Styled "Location: $downloadPath" -Color $Theme.Info -Prefix "Location" 96 | 97 | # Check if running with administrator privileges 98 | $isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) 99 | 100 | if (-not $isAdmin) { 101 | Write-Styled "Requesting administrator privileges..." -Color $Theme.Warning -Prefix "Admin" 102 | 103 | # Create new process with administrator privileges 104 | $startInfo = New-Object System.Diagnostics.ProcessStartInfo 105 | $startInfo.FileName = $downloadPath 106 | $startInfo.UseShellExecute = $true 107 | $startInfo.Verb = "runas" 108 | 109 | try { 110 | [System.Diagnostics.Process]::Start($startInfo) 111 | Write-Styled "Program started with admin privileges" -Color $Theme.Success -Prefix "Launch" 112 | return 113 | } 114 | catch { 115 | Write-Styled "Failed to start with admin privileges. Starting normally..." -Color $Theme.Warning -Prefix "Warning" 116 | Start-Process $downloadPath 117 | return 118 | } 119 | } 120 | 121 | # If already running with administrator privileges, start directly 122 | Start-Process $downloadPath 123 | return 124 | } 125 | 126 | Write-Styled "No existing installation file found, starting download..." -Color $Theme.Primary -Prefix "Download" 127 | 128 | # Create WebClient and add progress event 129 | $webClient = New-Object System.Net.WebClient 130 | $webClient.Headers.Add("User-Agent", "PowerShell Script") 131 | 132 | # Define progress variables 133 | $Global:downloadedBytes = 0 134 | $Global:totalBytes = 0 135 | $Global:lastProgress = 0 136 | $Global:lastBytes = 0 137 | $Global:lastTime = Get-Date 138 | 139 | # Download progress event 140 | $eventId = [guid]::NewGuid() 141 | Register-ObjectEvent -InputObject $webClient -EventName DownloadProgressChanged -Action { 142 | $Global:downloadedBytes = $EventArgs.BytesReceived 143 | $Global:totalBytes = $EventArgs.TotalBytesToReceive 144 | $progress = [math]::Round(($Global:downloadedBytes / $Global:totalBytes) * 100, 1) 145 | 146 | # Only update display when progress changes by more than 1% 147 | if ($progress -gt $Global:lastProgress + 1) { 148 | $Global:lastProgress = $progress 149 | $downloadedMB = [math]::Round($Global:downloadedBytes / 1MB, 2) 150 | $totalMB = [math]::Round($Global:totalBytes / 1MB, 2) 151 | 152 | # Calculate download speed 153 | $currentTime = Get-Date 154 | $timeSpan = ($currentTime - $Global:lastTime).TotalSeconds 155 | if ($timeSpan -gt 0) { 156 | $bytesChange = $Global:downloadedBytes - $Global:lastBytes 157 | $speed = $bytesChange / $timeSpan 158 | 159 | # Choose appropriate unit based on speed 160 | $speedDisplay = if ($speed -gt 1MB) { 161 | "$([math]::Round($speed / 1MB, 2)) MB/s" 162 | } elseif ($speed -gt 1KB) { 163 | "$([math]::Round($speed / 1KB, 2)) KB/s" 164 | } else { 165 | "$([math]::Round($speed, 2)) B/s" 166 | } 167 | 168 | Write-Host "`rDownloading: $downloadedMB MB / $totalMB MB ($progress%) - $speedDisplay" -NoNewline -ForegroundColor Cyan 169 | 170 | # Update last data 171 | $Global:lastBytes = $Global:downloadedBytes 172 | $Global:lastTime = $currentTime 173 | } 174 | } 175 | } | Out-Null 176 | 177 | # Download completed event 178 | Register-ObjectEvent -InputObject $webClient -EventName DownloadFileCompleted -Action { 179 | Write-Host "`r" -NoNewline 180 | Write-Styled "Download completed!" -Color $Theme.Success -Prefix "Complete" 181 | Unregister-Event -SourceIdentifier $eventId 182 | } | Out-Null 183 | 184 | # Start download 185 | $webClient.DownloadFileAsync([Uri]$asset.browser_download_url, $downloadPath) 186 | 187 | # Wait for download to complete 188 | while ($webClient.IsBusy) { 189 | Start-Sleep -Milliseconds 100 190 | } 191 | 192 | Write-Styled "File location: $downloadPath" -Color $Theme.Info -Prefix "Location" 193 | Write-Styled "Starting program..." -Color $Theme.Primary -Prefix "Launch" 194 | 195 | # Run program 196 | Start-Process $downloadPath 197 | } 198 | catch { 199 | Write-Styled $_.Exception.Message -Color $Theme.Error -Prefix "Error" 200 | throw 201 | } 202 | } 203 | 204 | # Execute installation 205 | try { 206 | Install-CursorFreeVIP 207 | } 208 | catch { 209 | Write-Styled "Download failed" -Color $Theme.Error -Prefix "Error" 210 | Write-Styled $_.Exception.Message -Color $Theme.Error 211 | } 212 | finally { 213 | Write-Host "`nPress any key to exit..." -ForegroundColor $Theme.Info 214 | $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') 215 | } 216 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Color definitions 4 | RED='\033[0;31m' 5 | GREEN='\033[0;32m' 6 | YELLOW='\033[1;33m' 7 | BLUE='\033[0;34m' 8 | CYAN='\033[0;36m' 9 | NC='\033[0m' # No Color 10 | 11 | # Logo 12 | print_logo() { 13 | echo -e "${CYAN}" 14 | cat << "EOF" 15 | ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗ 16 | ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ ██╔══██╗██╔══██╗██╔═══██╗ 17 | ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ ██████╔╝██████╔╝██║ ██║ 18 | ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ ██╔═══╝ ██╔══██╗██║ ██║ 19 | ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ ██║ ██║ ██║╚██████╔╝ 20 | ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ 21 | EOF 22 | echo -e "${NC}" 23 | } 24 | 25 | # Get download folder path 26 | get_downloads_dir() { 27 | if [[ "$(uname)" == "Darwin" ]]; then 28 | echo "$HOME/Downloads" 29 | else 30 | if [ -f "$HOME/.config/user-dirs.dirs" ]; then 31 | . "$HOME/.config/user-dirs.dirs" 32 | echo "${XDG_DOWNLOAD_DIR:-$HOME/Downloads}" 33 | else 34 | echo "$HOME/Downloads" 35 | fi 36 | fi 37 | } 38 | 39 | # Get latest version 40 | get_latest_version() { 41 | echo -e "${CYAN}ℹ️ Checking latest version...${NC}" 42 | latest_release=$(curl -s https://api.github.com/repos/yeongpin/cursor-free-vip/releases/latest) || { 43 | echo -e "${RED}❌ Cannot get latest version information${NC}" 44 | exit 1 45 | } 46 | 47 | VERSION=$(echo "$latest_release" | grep -o '"tag_name": ".*"' | cut -d'"' -f4 | tr -d 'v') 48 | if [ -z "$VERSION" ]; then 49 | echo -e "${RED}❌ Failed to parse version from GitHub API response:\n${latest_release}" 50 | exit 1 51 | fi 52 | 53 | echo -e "${GREEN}✅ Found latest version: ${VERSION}${NC}" 54 | } 55 | 56 | # Detect system type and architecture 57 | detect_os() { 58 | if [[ "$(uname)" == "Darwin" ]]; then 59 | # Detect macOS architecture 60 | ARCH=$(uname -m) 61 | if [[ "$ARCH" == "arm64" ]]; then 62 | OS="mac_arm64" 63 | echo -e "${CYAN}ℹ️ Detected macOS ARM64 architecture${NC}" 64 | else 65 | OS="mac_intel" 66 | echo -e "${CYAN}ℹ️ Detected macOS Intel architecture${NC}" 67 | fi 68 | elif [[ "$(uname)" == "Linux" ]]; then 69 | # Detect Linux architecture 70 | ARCH=$(uname -m) 71 | if [[ "$ARCH" == "aarch64" || "$ARCH" == "arm64" ]]; then 72 | OS="linux_arm64" 73 | echo -e "${CYAN}ℹ️ Detected Linux ARM64 architecture${NC}" 74 | else 75 | OS="linux_x64" 76 | echo -e "${CYAN}ℹ️ Detected Linux x64 architecture${NC}" 77 | fi 78 | else 79 | # Assume Windows 80 | OS="windows" 81 | echo -e "${CYAN}ℹ️ Detected Windows system${NC}" 82 | fi 83 | } 84 | 85 | # Install and download 86 | install_cursor_free_vip() { 87 | local downloads_dir=$(get_downloads_dir) 88 | local binary_name="CursorFreeVIP_${VERSION}_${OS}" 89 | local binary_path="${downloads_dir}/${binary_name}" 90 | local download_url="https://github.com/yeongpin/cursor-free-vip/releases/download/v${VERSION}/${binary_name}" 91 | 92 | # Check if file already exists 93 | if [ -f "${binary_path}" ]; then 94 | echo -e "${GREEN}✅ Found existing installation file${NC}" 95 | echo -e "${CYAN}ℹ️ Location: ${binary_path}${NC}" 96 | 97 | # Check if running as root 98 | if [ "$EUID" -ne 0 ]; then 99 | echo -e "${YELLOW}⚠️ Requesting administrator privileges...${NC}" 100 | if command -v sudo >/dev/null 2>&1; then 101 | echo -e "${CYAN}ℹ️ Starting program with sudo...${NC}" 102 | sudo chmod +x "${binary_path}" 103 | sudo "${binary_path}" 104 | else 105 | echo -e "${YELLOW}⚠️ sudo not found, trying to run normally...${NC}" 106 | chmod +x "${binary_path}" 107 | "${binary_path}" 108 | fi 109 | else 110 | # Already running as root 111 | echo -e "${CYAN}ℹ️ Already running as root, starting program...${NC}" 112 | chmod +x "${binary_path}" 113 | "${binary_path}" 114 | fi 115 | return 116 | fi 117 | 118 | echo -e "${CYAN}ℹ️ No existing installation file found, starting download...${NC}" 119 | echo -e "${CYAN}ℹ️ Downloading to ${downloads_dir}...${NC}" 120 | echo -e "${CYAN}ℹ️ Download link: ${download_url}${NC}" 121 | 122 | # Check if file exists 123 | if curl --output /dev/null --silent --head --fail "$download_url"; then 124 | echo -e "${GREEN}✅ File exists, starting download...${NC}" 125 | else 126 | echo -e "${RED}❌ Download link does not exist: ${download_url}${NC}" 127 | echo -e "${YELLOW}⚠️ Trying without architecture...${NC}" 128 | 129 | # Try without architecture 130 | if [[ "$OS" == "mac_arm64" || "$OS" == "mac_intel" ]]; then 131 | OS="mac" 132 | binary_name="CursorFreeVIP_${VERSION}_${OS}" 133 | download_url="https://github.com/yeongpin/cursor-free-vip/releases/download/v${VERSION}/${binary_name}" 134 | echo -e "${CYAN}ℹ️ New download link: ${download_url}${NC}" 135 | 136 | if ! curl --output /dev/null --silent --head --fail "$download_url"; then 137 | echo -e "${RED}❌ New download link does not exist${NC}" 138 | exit 1 139 | fi 140 | elif [[ "$OS" == "linux_x64" || "$OS" == "linux_arm64" ]]; then 141 | OS="linux" 142 | binary_name="CursorFreeVIP_${VERSION}_${OS}" 143 | download_url="https://github.com/yeongpin/cursor-free-vip/releases/download/v${VERSION}/${binary_name}" 144 | echo -e "${CYAN}ℹ️ New download link: ${download_url}${NC}" 145 | 146 | if ! curl --output /dev/null --silent --head --fail "$download_url"; then 147 | echo -e "${RED}❌ New download link does not exist${NC}" 148 | exit 1 149 | fi 150 | else 151 | exit 1 152 | fi 153 | fi 154 | 155 | # Download file 156 | if ! curl -L -o "${binary_path}" "$download_url"; then 157 | echo -e "${RED}❌ Download failed${NC}" 158 | exit 1 159 | fi 160 | 161 | # Check downloaded file size 162 | local file_size=$(stat -f%z "${binary_path}" 2>/dev/null || stat -c%s "${binary_path}" 2>/dev/null) 163 | echo -e "${CYAN}ℹ️ Downloaded file size: ${file_size} bytes${NC}" 164 | 165 | # If file is too small, it might be an error message 166 | if [ "$file_size" -lt 1000 ]; then 167 | echo -e "${YELLOW}⚠️ Warning: Downloaded file is too small, possibly not a valid executable file${NC}" 168 | echo -e "${YELLOW}⚠️ File content:${NC}" 169 | cat "${binary_path}" 170 | echo "" 171 | echo -e "${RED}❌ Download failed, please check version and operating system${NC}" 172 | exit 1 173 | fi 174 | 175 | echo -e "${CYAN}ℹ️ Setting executable permissions...${NC}" 176 | if chmod +x "${binary_path}"; then 177 | echo -e "${GREEN}✅ Installation completed!${NC}" 178 | echo -e "${CYAN}ℹ️ Program downloaded to: ${binary_path}${NC}" 179 | echo -e "${CYAN}ℹ️ Starting program...${NC}" 180 | 181 | # Run program directly 182 | "${binary_path}" 183 | else 184 | echo -e "${RED}❌ Installation failed${NC}" 185 | exit 1 186 | fi 187 | } 188 | 189 | # Main program 190 | main() { 191 | print_logo 192 | get_latest_version 193 | detect_os 194 | install_cursor_free_vip 195 | } 196 | 197 | # Run main program 198 | main 199 | -------------------------------------------------------------------------------- /scripts/reset.ps1: -------------------------------------------------------------------------------- 1 | # 檢查是否是通過權限提升啟動的 2 | param( 3 | [switch]$Elevated 4 | ) 5 | 6 | # 設置顏色主題 7 | $Theme = @{ 8 | Primary = 'Cyan' 9 | Success = 'Green' 10 | Warning = 'Yellow' 11 | Error = 'Red' 12 | Info = 'White' 13 | } 14 | 15 | # ASCII Logo 16 | $Logo = @" 17 | ██████╗ ███████╗███████╗███████╗████████╗ ████████╗ ██████╗ ██████╗ ██╗ 18 | ██╔══██╗██╔════╝██╔════╝██╔════╝╚══██╔══╝ ╚══██╔══╝██╔═══██╗██╔═══██╗██║ 19 | ██████╔╝█████╗ ███████╗█████╗ ██║ ██║ ██║ ██║██║ ██║██║ 20 | ██╔══██╗██╔══╝ ╚════██║██╔══╝ ██║ ██║ ██║ ██║██║ ██║██║ 21 | ██║ ██║███████╗███████║███████╗ ██║ ██║ ╚██████╔╝╚██████╔╝███████╗ 22 | ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ 23 | "@ 24 | 25 | # 美化輸出函數 26 | function Write-Styled { 27 | param ( 28 | [string]$Message, 29 | [string]$Color = $Theme.Info, 30 | [string]$Prefix = "", 31 | [switch]$NoNewline 32 | ) 33 | $emoji = switch ($Color) { 34 | $Theme.Success { "✅" } 35 | $Theme.Error { "❌" } 36 | $Theme.Warning { "⚠️" } 37 | default { "ℹ️" } 38 | } 39 | 40 | $output = if ($Prefix) { "$emoji $Prefix :: $Message" } else { "$emoji $Message" } 41 | if ($NoNewline) { 42 | Write-Host $output -ForegroundColor $Color -NoNewline 43 | } else { 44 | Write-Host $output -ForegroundColor $Color 45 | } 46 | } 47 | 48 | # 檢查管理員權限 49 | $isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") 50 | if (-NOT $isAdmin) { 51 | Write-Styled "需要管理員權限來運行重置工具" -Color $Theme.Warning -Prefix "權限" 52 | Write-Styled "正在請求管理員權限..." -Color $Theme.Primary -Prefix "提升" 53 | 54 | # 顯示操作選項 55 | Write-Host "`n選擇操作:" -ForegroundColor $Theme.Primary 56 | Write-Host "1. 請求管理員權限" -ForegroundColor $Theme.Info 57 | Write-Host "2. 退出程序" -ForegroundColor $Theme.Info 58 | 59 | $choice = Read-Host "`n請輸入選項 (1-2)" 60 | 61 | if ($choice -ne "1") { 62 | Write-Styled "操作已取消" -Color $Theme.Warning -Prefix "取消" 63 | Write-Host "`n按任意鍵退出..." -ForegroundColor $Theme.Info 64 | $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') 65 | exit 66 | } 67 | 68 | try { 69 | Start-Process powershell.exe -Verb RunAs -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" -Elevated" 70 | exit 71 | } 72 | catch { 73 | Write-Styled "無法獲取管理員權限" -Color $Theme.Error -Prefix "錯誤" 74 | Write-Styled "請以管理員身份運行 PowerShell 後重試" -Color $Theme.Warning -Prefix "提示" 75 | Write-Host "`n按任意鍵退出..." -ForegroundColor $Theme.Info 76 | $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') 77 | exit 1 78 | } 79 | } 80 | 81 | # 如果是提升權限後的窗口,等待一下確保窗口可見 82 | if ($Elevated) { 83 | Start-Sleep -Seconds 1 84 | } 85 | 86 | # 顯示 Logo 87 | Write-Host $Logo -ForegroundColor $Theme.Primary 88 | Write-Host "Created by YeongPin`n" -ForegroundColor $Theme.Info 89 | 90 | # 設置 TLS 1.2 91 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 92 | 93 | # 創建臨時目錄 94 | $TmpDir = Join-Path $env:TEMP ([System.Guid]::NewGuid().ToString()) 95 | New-Item -ItemType Directory -Path $TmpDir -Force | Out-Null 96 | 97 | # 清理函數 98 | function Cleanup { 99 | if (Test-Path $TmpDir) { 100 | Remove-Item -Recurse -Force $TmpDir -ErrorAction SilentlyContinue 101 | } 102 | } 103 | 104 | try { 105 | # 下載地址 106 | $url = "https://github.com/yeongpin/cursor-free-vip/releases/download/ManualReset/reset_machine_manual.exe" 107 | $output = Join-Path $TmpDir "reset_machine_manual.exe" 108 | 109 | # 下載文件 110 | Write-Styled "正在下載重置工具..." -Color $Theme.Primary -Prefix "下載" 111 | Invoke-WebRequest -Uri $url -OutFile $output 112 | Write-Styled "下載完成!" -Color $Theme.Success -Prefix "完成" 113 | 114 | # 執行重置工具 115 | Write-Styled "正在啟動重置工具..." -Color $Theme.Primary -Prefix "執行" 116 | Start-Process -FilePath $output -Wait 117 | Write-Styled "重置完成!" -Color $Theme.Success -Prefix "完成" 118 | } 119 | catch { 120 | Write-Styled "操作失敗" -Color $Theme.Error -Prefix "錯誤" 121 | Write-Styled $_.Exception.Message -Color $Theme.Error 122 | } 123 | finally { 124 | Cleanup 125 | Write-Host "`n按任意鍵退出..." -ForegroundColor $Theme.Info 126 | $null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') 127 | } -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import platform 4 | import random 5 | 6 | def get_user_documents_path(): 7 | """Get user documents path""" 8 | if platform.system() == "Windows": 9 | return os.path.expanduser("~\\Documents") 10 | else: 11 | return os.path.expanduser("~/Documents") 12 | 13 | def get_default_driver_path(browser_type='chrome'): 14 | """Get default driver path based on browser type""" 15 | browser_type = browser_type.lower() 16 | if browser_type == 'chrome': 17 | return get_default_chrome_driver_path() 18 | elif browser_type == 'edge': 19 | return get_default_edge_driver_path() 20 | elif browser_type == 'firefox': 21 | return get_default_firefox_driver_path() 22 | elif browser_type == 'brave': 23 | # Brave 使用 Chrome 的 driver 24 | return get_default_chrome_driver_path() 25 | else: 26 | # Default to Chrome if browser type is unknown 27 | return get_default_chrome_driver_path() 28 | 29 | def get_default_chrome_driver_path(): 30 | """Get default Chrome driver path""" 31 | if sys.platform == "win32": 32 | return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "chromedriver.exe") 33 | elif sys.platform == "darwin": 34 | return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "chromedriver") 35 | else: 36 | return "/usr/local/bin/chromedriver" 37 | 38 | def get_default_edge_driver_path(): 39 | """Get default Edge driver path""" 40 | if sys.platform == "win32": 41 | return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "msedgedriver.exe") 42 | elif sys.platform == "darwin": 43 | return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "msedgedriver") 44 | else: 45 | return "/usr/local/bin/msedgedriver" 46 | 47 | def get_default_firefox_driver_path(): 48 | """Get default Firefox driver path""" 49 | if sys.platform == "win32": 50 | return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "geckodriver.exe") 51 | elif sys.platform == "darwin": 52 | return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "geckodriver") 53 | else: 54 | return "/usr/local/bin/geckodriver" 55 | 56 | def get_default_brave_driver_path(): 57 | """Get default Brave driver path (uses Chrome driver)""" 58 | # Brave 浏览器基于 Chromium,所以使用相同的 chromedriver 59 | return get_default_chrome_driver_path() 60 | 61 | def get_default_browser_path(browser_type='chrome'): 62 | """Get default browser executable path""" 63 | browser_type = browser_type.lower() 64 | 65 | if sys.platform == "win32": 66 | if browser_type == 'chrome': 67 | # 尝试在 PATH 中找到 Chrome 68 | try: 69 | import shutil 70 | chrome_in_path = shutil.which("chrome") 71 | if chrome_in_path: 72 | return chrome_in_path 73 | except: 74 | pass 75 | # 使用默认路径 76 | return r"C:\Program Files\Google\Chrome\Application\chrome.exe" 77 | elif browser_type == 'edge': 78 | return r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" 79 | elif browser_type == 'firefox': 80 | return r"C:\Program Files\Mozilla Firefox\firefox.exe" 81 | elif browser_type == 'opera': 82 | # 尝试多个可能的 Opera 路径 83 | opera_paths = [ 84 | r"C:\Program Files\Opera\opera.exe", 85 | r"C:\Program Files (x86)\Opera\opera.exe", 86 | os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera', 'launcher.exe'), 87 | os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera', 'opera.exe') 88 | ] 89 | for path in opera_paths: 90 | if os.path.exists(path): 91 | return path 92 | return opera_paths[0] # 返回第一个路径,即使它不存在 93 | elif browser_type == 'operagx': 94 | # 尝试多个可能的 Opera GX 路径 95 | operagx_paths = [ 96 | os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera GX', 'launcher.exe'), 97 | os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera GX', 'opera.exe'), 98 | r"C:\Program Files\Opera GX\opera.exe", 99 | r"C:\Program Files (x86)\Opera GX\opera.exe" 100 | ] 101 | for path in operagx_paths: 102 | if os.path.exists(path): 103 | return path 104 | return operagx_paths[0] # 返回第一个路径,即使它不存在 105 | elif browser_type == 'brave': 106 | # Brave 浏览器的默认安装路径 107 | paths = [ 108 | os.path.join(os.environ.get('PROGRAMFILES', ''), 'BraveSoftware/Brave-Browser/Application/brave.exe'), 109 | os.path.join(os.environ.get('PROGRAMFILES(X86)', ''), 'BraveSoftware/Brave-Browser/Application/brave.exe'), 110 | os.path.join(os.environ.get('LOCALAPPDATA', ''), 'BraveSoftware/Brave-Browser/Application/brave.exe') 111 | ] 112 | for path in paths: 113 | if os.path.exists(path): 114 | return path 115 | return paths[0] # 返回第一个路径,即使它不存在 116 | 117 | elif sys.platform == "darwin": 118 | if browser_type == 'chrome': 119 | return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" 120 | elif browser_type == 'edge': 121 | return "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge" 122 | elif browser_type == 'firefox': 123 | return "/Applications/Firefox.app/Contents/MacOS/firefox" 124 | elif browser_type == 'brave': 125 | return "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser" 126 | elif browser_type == 'opera': 127 | return "/Applications/Opera.app/Contents/MacOS/Opera" 128 | elif browser_type == 'operagx': 129 | return "/Applications/Opera GX.app/Contents/MacOS/Opera" 130 | 131 | else: # Linux 132 | if browser_type == 'chrome': 133 | # 尝试多种可能的名称 134 | chrome_names = ["google-chrome", "chrome", "chromium", "chromium-browser"] 135 | for name in chrome_names: 136 | try: 137 | import shutil 138 | path = shutil.which(name) 139 | if path: 140 | return path 141 | except: 142 | pass 143 | return "/usr/bin/google-chrome" 144 | elif browser_type == 'edge': 145 | return "/usr/bin/microsoft-edge" 146 | elif browser_type == 'firefox': 147 | return "/usr/bin/firefox" 148 | elif browser_type == 'opera': 149 | return "/usr/bin/opera" 150 | elif browser_type == 'operagx': 151 | # 尝试常见的 Opera GX 路径 152 | operagx_names = ["opera-gx"] 153 | for name in operagx_names: 154 | try: 155 | import shutil 156 | path = shutil.which(name) 157 | if path: 158 | return path 159 | except: 160 | pass 161 | return "/usr/bin/opera-gx" 162 | elif browser_type == 'brave': 163 | # 尝试常见的 Brave 路径 164 | brave_names = ["brave", "brave-browser"] 165 | for name in brave_names: 166 | try: 167 | import shutil 168 | path = shutil.which(name) 169 | if path: 170 | return path 171 | except: 172 | pass 173 | return "/usr/bin/brave-browser" 174 | 175 | # 如果找不到指定的浏览器类型,则返回 Chrome 的路径 176 | return get_default_browser_path('chrome') 177 | 178 | def get_linux_cursor_path(): 179 | """Get Linux Cursor path""" 180 | possible_paths = [ 181 | "/opt/Cursor/resources/app", 182 | "/usr/share/cursor/resources/app", 183 | "/opt/cursor-bin/resources/app", 184 | "/usr/lib/cursor/resources/app", 185 | os.path.expanduser("~/.local/share/cursor/resources/app") 186 | ] 187 | 188 | # return the first path that exists 189 | return next((path for path in possible_paths if os.path.exists(path)), possible_paths[0]) 190 | 191 | def get_random_wait_time(config, timing_key): 192 | """Get random wait time based on configuration timing settings 193 | 194 | Args: 195 | config (dict): Configuration dictionary containing timing settings 196 | timing_key (str): Key to look up in the timing settings 197 | 198 | Returns: 199 | float: Random wait time in seconds 200 | """ 201 | try: 202 | # Get timing value from config 203 | timing = config.get('Timing', {}).get(timing_key) 204 | if not timing: 205 | # Default to 0.5-1.5 seconds if timing not found 206 | return random.uniform(0.5, 1.5) 207 | 208 | # Check if timing is a range (e.g., "0.5-1.5" or "0.5,1.5") 209 | if isinstance(timing, str): 210 | if '-' in timing: 211 | min_time, max_time = map(float, timing.split('-')) 212 | elif ',' in timing: 213 | min_time, max_time = map(float, timing.split(',')) 214 | else: 215 | # Single value, use it as both min and max 216 | min_time = max_time = float(timing) 217 | else: 218 | # If timing is a number, use it as both min and max 219 | min_time = max_time = float(timing) 220 | 221 | return random.uniform(min_time, max_time) 222 | 223 | except (ValueError, TypeError, AttributeError): 224 | # Return default value if any error occurs 225 | return random.uniform(0.5, 1.5) --------------------------------------------------------------------------------