Compare commits

...

10 Commits

Author SHA1 Message Date
huangzhenpc
96604f7139 first commit: 初始化项目,添加基本功能和打包脚本
Some checks failed
Remove old artifacts / remove-old-artifacts (push) Has been cancelled
2025-02-17 18:58:01 +08:00
chengchongzhen
351b12a039 chore: Update QQ group contact information 2025-02-16 15:38:23 +08:00
Journey
ca8cc199e9 Update README.md 2025-02-15 20:30:49 +08:00
cheng zhen
0c9549bcdf feat: Add end message with project information and contact details 2025-02-14 07:31:19 +08:00
cheng zhen
39e0fbd305 chore: 一些无关紧要的逻辑
(cherry picked from commit e82fc56c199ceaf2eef0062e6b7aefb0d50addc7)
2025-02-14 07:14:18 +08:00
Journey
7289d11749 Merge pull request #209 from Rygtx/main
feat: Integrate go-cursor-help for machine ID reset and update README with project acknowledgments
2025-02-14 07:07:55 +08:00
Rygtx
793920e6ca feat: Integrate go-cursor-help for machine ID reset and update README with project acknowledgments 2025-02-14 04:46:23 +08:00
cheng zhen
840c4393d2 feat: Enable ExitCursor() call in main execution block 2025-02-13 21:59:44 +08:00
cheng zhen
9f89dcba7b refactor: Simplify machine ID reset process with user guidance 2025-02-13 21:50:40 +08:00
cheng zhen
15541a9590 feat: Add GitHub project link in Turnstile verification error logging 2025-02-13 21:49:45 +08:00
12 changed files with 1085 additions and 6 deletions

View File

@@ -38,4 +38,10 @@ Please refer to our [online documentation](https://cursor-auto-free-doc.vercel.a
- **2025-01-11**: Added headless mode and proxy configuration through .env file.
- **2025-01-20**: Added IMAP to replace tempmail.plus.
## Special Thanks
This project has received support and help from many open source projects and community members. We would like to express our special gratitude to:
### Open Source Projects
- [go-cursor-help](https://github.com/yuaotian/go-cursor-help) - An excellent Cursor machine code reset tool with 9.1k Stars. Our machine code reset functionality is implemented using this project, which is one of the most popular Cursor auxiliary tools.
Inspired by [gpt-cursor-auto](https://github.com/hmhm2022/gpt-cursor-auto); optimized verification and email auto-registration logic; solved the issue of not being able to receive email verification codes.

View File

@@ -1,9 +1,75 @@
# 听泉助手
一个用于管理Cursor编辑器授权的跨平台桌面应用。
## 功能特点
- 设备ID管理
- 会员状态显示
- 激活码管理
- Cursor编辑器授权刷新
- 版本限制实现
- 更新控制
## 开发环境要求
- Python 3.9+
- PyQt5
- 其他依赖见 requirements.txt
## 安装依赖
```bash
# 创建虚拟环境
python -m venv venv
# 激活虚拟环境
# Windows:
venv\Scripts\activate
# Mac/Linux:
source venv/bin/activate
# 安装依赖
pip install -r requirements.txt
```
## 打包说明
### Windows版本
```bash
# 运行打包脚本
build_win.bat
```
### Mac版本
```bash
# 添加执行权限
chmod +x build_mac.command
# 运行打包脚本
./build_mac.command
```
## 项目结构
```
.
├── README.md
├── requirements.txt
├── build_mac.command # Mac打包脚本
├── build_win.bat # Windows打包脚本
├── gui/
│ └── main_window.py # 主窗口界面
├── logger.py # 日志模块
├── update_cursor_token.py # 授权更新模块
└── ... # 其他模块
```
# Cursor Pro 自动化工具使用说明
[English doc](./README.EN.md)
## 交流群 QQ 576045098
## 交流群 QQ 1034718338
## 在线文档
@@ -30,7 +96,11 @@
## 感谢 linuxDo 这个开源社区(一个真正的技术社区)
https://linux.do/
## 特别鸣谢
本项目的开发过程中得到了众多开源项目和社区成员的支持与帮助,在此特别感谢:
### 开源项目
- [go-cursor-help](https://github.com/yuaotian/go-cursor-help) - 一个优秀的 Cursor 机器码重置工具,本项目的机器码重置功能使用该项目实现。该项目目前已获得 9.1k Stars是最受欢迎的 Cursor 辅助工具之一。
## 请我喝杯茶
<img src="./screen/28613e3f3f23a935b66a7ba31ff4e3f.jpg" width="300"/> <img src="./screen/mm_facetoface_collect_qrcode_1738583247120.png" width="300"/>

61
build_mac.command Normal file
View File

@@ -0,0 +1,61 @@
#!/bin/bash
# 获取脚本所在目录
cd "$(dirname "$0")"
# 检查虚拟环境
if [ -f "venv/bin/activate" ]; then
echo "激活虚拟环境..."
source venv/bin/activate
else
echo "警告: 未找到虚拟环境,创建新的虚拟环境..."
python3 -m venv venv
source venv/bin/activate
echo "安装依赖..."
pip3 install --upgrade pip
pip3 install pyinstaller
pip3 install PyQt5
pip3 install requests
pip3 install urllib3
fi
# 确保依赖已安装
echo "检查依赖..."
pip3 list | grep -E "pyinstaller|PyQt5|requests|urllib3" || {
echo "安装缺失的依赖..."
pip3 install pyinstaller PyQt5 requests urllib3
}
# 执行打包
echo "开始打包..."
python3 -m PyInstaller \
--name="听泉助手" \
--windowed \
--clean \
--noconfirm \
--add-data="logger.py:." \
--add-data="update_cursor_token.py:." \
--add-data="cursor_auth_manager.py:." \
--add-data="reset_machine.py:." \
--add-data="patch_cursor_get_machine_id.py:." \
--add-data="exit_cursor.py:." \
--add-data="go_cursor_help.py:." \
--add-data="logo.py:." \
--hidden-import=PyQt5 \
--hidden-import=requests \
--hidden-import=urllib3 \
--target-architecture=universal2 \
--codesign-identity=- \
--osx-bundle-identifier=com.tingquan.helper \
gui/main_window.py
# 检查打包结果
if [ -d "dist/听泉助手.app" ]; then
echo "打包成功!应用程序包已生成在 dist/听泉助手.app"
else
echo "打包失败,请检查错误信息"
fi
# 退出虚拟环境
deactivate

35
build_mac.py Normal file
View File

@@ -0,0 +1,35 @@
import PyInstaller.__main__
import os
import sys
# 获取当前脚本所在目录
current_dir = os.path.dirname(os.path.abspath(__file__))
# 打包参数
params = [
'gui/main_window.py', # 主程序入口
'--name=听泉助手', # 应用名称
'--onefile', # 打包成单个文件
'--windowed', # 不显示控制台窗口
'--clean', # 清理临时文件
'--noconfirm', # 不确认覆盖
f'--distpath={os.path.join(current_dir, "dist")}', # 输出目录
f'--workpath={os.path.join(current_dir, "build")}', # 工作目录
f'--specpath={current_dir}', # spec文件目录
'--add-data=logger.py;.', # 添加额外文件
'--add-data=update_cursor_token.py;.',
'--add-data=cursor_auth_manager.py;.',
'--add-data=reset_machine.py;.',
'--add-data=patch_cursor_get_machine_id.py;.',
'--add-data=exit_cursor.py;.',
'--add-data=go_cursor_help.py;.',
'--add-data=logo.py;.',
'--hidden-import=PyQt5',
'--hidden-import=requests',
'--hidden-import=urllib3',
]
# 执行打包
PyInstaller.__main__.run(params)
print("打包完成可执行文件已生成在dist目录下。")

17
build_mac_docker.bat Normal file
View File

@@ -0,0 +1,17 @@
@echo off
setlocal
echo 构建Docker镜像...
docker build -t tingquan-mac-builder .
echo 运行Docker容器进行打包...
docker run --rm -v "%cd%":/app tingquan-mac-builder
echo 检查打包结果...
if exist "dist\听泉助手.app" (
echo 打包成功Mac应用程序包已生成在 dist\听泉助手.app
) else (
echo 打包失败,请检查错误信息
)
endlocal

54
build_win.bat Normal file
View File

@@ -0,0 +1,54 @@
@echo off
setlocal
:: 获取脚本所在目录
cd /d "%~dp0"
:: 检查虚拟环境
if exist "venv\Scripts\activate.bat" (
echo Activating virtual environment...
call venv\Scripts\activate.bat
) else (
echo Creating new virtual environment...
python -m venv venv
call venv\Scripts\activate.bat
echo Installing dependencies...
python -m pip install --upgrade pip
pip install pyinstaller
pip install PyQt5
pip install requests
pip install urllib3
)
:: 执行打包
echo Starting build process...
python -m PyInstaller ^
--name="TingquanHelper" ^
--onefile ^
--windowed ^
--clean ^
--noconfirm ^
--add-data="logger.py;." ^
--add-data="update_cursor_token.py;." ^
--add-data="cursor_auth_manager.py;." ^
--add-data="reset_machine.py;." ^
--add-data="patch_cursor_get_machine_id.py;." ^
--add-data="exit_cursor.py;." ^
--add-data="go_cursor_help.py;." ^
--add-data="logo.py;." ^
--hidden-import=PyQt5 ^
--hidden-import=requests ^
--hidden-import=urllib3 ^
gui/main_window.py
:: 检查打包结果
if exist "dist\TingquanHelper.exe" (
echo Build successful! Executable created at dist\TingquanHelper.exe
) else (
echo Build failed. Please check the error messages.
)
:: 退出虚拟环境
deactivate
endlocal

35
build_win.py Normal file
View File

@@ -0,0 +1,35 @@
import PyInstaller.__main__
import os
import sys
# 获取当前脚本所在目录
current_dir = os.path.dirname(os.path.abspath(__file__))
# 打包参数
params = [
'gui/main_window.py', # 主程序入口
'--name=听泉助手', # 应用名称
'--onefile', # 打包成单个文件
'--windowed', # 不显示控制台窗口
'--clean', # 清理临时文件
'--noconfirm', # 不确认覆盖
f'--distpath={os.path.join(current_dir, "dist")}', # 输出目录
f'--workpath={os.path.join(current_dir, "build")}', # 工作目录
f'--specpath={current_dir}', # spec文件目录
'--add-data=logger.py;.', # 添加额外文件
'--add-data=update_cursor_token.py;.',
'--add-data=cursor_auth_manager.py;.',
'--add-data=reset_machine.py;.',
'--add-data=patch_cursor_get_machine_id.py;.',
'--add-data=exit_cursor.py;.',
'--add-data=go_cursor_help.py;.',
'--add-data=logo.py;.',
'--hidden-import=PyQt5',
'--hidden-import=requests',
'--hidden-import=urllib3',
]
# 执行打包
PyInstaller.__main__.run(params)
print("打包完成可执行文件已生成在dist目录下。")

View File

@@ -7,6 +7,7 @@ from enum import Enum
from typing import Optional
from exit_cursor import ExitCursor
import go_cursor_help
import patch_cursor_get_machine_id
from reset_machine import MachineIDResetter
@@ -149,6 +150,9 @@ def handle_turnstile(tab, max_retries: int = 2, retry_interval: tuple = (1, 2))
# 超出最大重试次数
logging.error(f"验证失败 - 已达到最大重试次数 {max_retries}")
logging.error(
"请前往开源项目查看更多信息https://github.com/chengazhen/cursor-auto-free"
)
save_screenshot(tab, "failed")
return False
@@ -298,6 +302,9 @@ def sign_up_account(browser, tab):
usage_info = usage_ele.text
total_usage = usage_info.split("/")[-1].strip()
logging.info(f"账户可用额度上限: {total_usage}")
logging.info(
"请前往开源项目查看更多信息https://github.com/chengazhen/cursor-auto-free"
)
except Exception as e:
logging.error(f"获取账户额度信息失败: {str(e)}")
@@ -374,18 +381,31 @@ def check_cursor_version():
def reset_machine_id(greater_than_0_45):
if greater_than_0_45:
# 提示请手动执行脚本 https://github.com/chengazhen/cursor-auto-free/blob/main/patch_cursor_get_machine_id.py
patch_cursor_get_machine_id.patch_cursor_get_machine_id()
go_cursor_help.go_cursor_help()
else:
MachineIDResetter().reset_machine_ids()
def print_end_message():
logging.info("\n\n\n\n\n")
logging.info("=" * 30)
logging.info("所有操作已完成")
logging.info("\n=== 获取更多信息 ===")
logging.info("🔥 QQ交流群: 1034718338")
logging.info("📺 B站UP主: 想回家的前端")
logging.info("=" * 30)
logging.info(
"请前往开源项目查看更多信息https://github.com/chengazhen/cursor-auto-free"
)
if __name__ == "__main__":
print_logo()
greater_than_0_45 = check_cursor_version()
browser_manager = None
try:
logging.info("\n=== 初始化程序 ===")
# ExitCursor()
ExitCursor()
# 提示用户选择操作模式
print("\n请选择操作模式:")
@@ -406,6 +426,7 @@ if __name__ == "__main__":
# 仅执行重置机器码
reset_machine_id(greater_than_0_45)
logging.info("机器码重置完成")
print_end_message()
sys.exit(0)
logging.info("正在初始化浏览器...")
@@ -427,7 +448,9 @@ if __name__ == "__main__":
logging.info("正在初始化邮箱验证模块...")
email_handler = EmailVerificationHandler()
logging.info(
"请前往开源项目查看更多信息https://github.com/chengazhen/cursor-auto-free"
)
logging.info("\n=== 配置信息 ===")
login_url = "https://authenticator.cursor.sh"
sign_up_url = "https://authenticator.cursor.sh/sign-up"
@@ -460,10 +483,13 @@ if __name__ == "__main__":
update_cursor_auth(
email=account, access_token=token, refresh_token=token
)
logging.info(
"请前往开源项目查看更多信息https://github.com/chengazhen/cursor-auto-free"
)
logging.info("重置机器码...")
reset_machine_id(greater_than_0_45)
logging.info("所有操作已完成")
print_end_message()
else:
logging.error("获取会话令牌失败,注册流程未完成")

29
go_cursor_help.py Normal file
View File

@@ -0,0 +1,29 @@
import platform
import os
import subprocess
from logger import logging
def go_cursor_help():
system = platform.system()
logging.info(f"当前操作系统: {system}")
base_url = "https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/yuaotian/go-cursor-help/refs/heads/master/scripts/run"
if system == "Darwin": # macOS
cmd = f'curl -fsSL {base_url}/cursor_mac_id_modifier.sh | sudo bash'
logging.info("执行macOS命令")
os.system(cmd)
elif system == "Linux":
cmd = f'curl -fsSL {base_url}/cursor_linux_id_modifier.sh | sudo bash'
logging.info("执行Linux命令")
os.system(cmd)
elif system == "Windows":
cmd = f'irm {base_url}/cursor_win_id_modifier.ps1 | iex'
logging.info("执行Windows命令")
# 在Windows上使用PowerShell执行命令
subprocess.run(["powershell", "-Command", cmd], shell=True)
else:
logging.error(f"不支持的操作系统: {system}")
return False
return True

405
gui/main_window.py Normal file
View File

@@ -0,0 +1,405 @@
import sys
import os
# 添加父目录到系统路径
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
sys.path.append(parent_dir)
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QPushButton, QLabel, QLineEdit, QTextEdit, QMessageBox,
QHBoxLayout, QFrame)
from PyQt5.QtCore import Qt, QThread, pyqtSignal
from PyQt5.QtGui import QFont, QIcon
from update_cursor_token import CursorTokenUpdater
from logger import logging
class UpdateWorker(QThread):
"""后台更新线程"""
finished = pyqtSignal(bool, str)
progress = pyqtSignal(str)
def __init__(self, updater):
super().__init__()
self.updater = updater
def run(self):
try:
success = self.updater.full_update_process()
if success:
self.finished.emit(True, "更新成功!")
else:
self.finished.emit(False, "更新失败,请查看日志")
except Exception as e:
self.finished.emit(False, f"发生错误: {str(e)}")
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
try:
logging.info("正在初始化主窗口...")
self.updater = CursorTokenUpdater()
self.init_ui()
logging.info("主窗口初始化完成")
except Exception as e:
logging.error(f"初始化主窗口时发生错误: {str(e)}")
QMessageBox.critical(self, "错误", f"初始化失败: {str(e)}")
def init_ui(self):
# 设置窗口基本属性
self.setWindowTitle('听泉助手')
self.setMinimumSize(600, 500)
self.setStyleSheet("""
QMainWindow {
background-color: #f5f5f7;
}
QPushButton {
background-color: #0066cc;
color: white;
border: none;
border-radius: 6px;
padding: 10px;
font-size: 14px;
min-width: 120px;
}
QPushButton:hover {
background-color: #0052a3;
}
QPushButton:pressed {
background-color: #003d7a;
}
QLabel {
font-size: 14px;
color: #333333;
}
QTextEdit {
border: 1px solid #cccccc;
border-radius: 6px;
padding: 10px;
background-color: white;
font-family: Consolas, Monaco, monospace;
}
QLineEdit {
border: 1px solid #cccccc;
border-radius: 6px;
padding: 8px;
background-color: white;
}
QMessageBox {
background-color: #f5f5f7;
}
QMessageBox QPushButton {
min-width: 80px;
padding: 5px 15px;
}
""")
try:
# 创建主窗口部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
layout.setSpacing(20)
layout.setContentsMargins(30, 30, 30, 30)
# 设备ID显示区域
device_frame = QFrame()
device_frame.setStyleSheet("""
QFrame {
background-color: white;
border-radius: 10px;
padding: 10px;
}
""")
device_layout = QHBoxLayout(device_frame)
device_layout.setContentsMargins(10, 5, 10, 5)
device_id_label = QLabel("设备标识:")
self.device_id_text = QLineEdit()
self.device_id_text.setReadOnly(True)
try:
hardware_id = self.updater.hardware_id
logging.info(f"获取到硬件ID: {hardware_id}")
self.device_id_text.setText(hardware_id)
except Exception as e:
logging.error(f"获取硬件ID失败: {str(e)}")
self.device_id_text.setText("获取失败")
copy_button = QPushButton("复制ID")
copy_button.setMaximumWidth(80)
copy_button.clicked.connect(self.copy_device_id)
device_layout.addWidget(device_id_label)
device_layout.addWidget(self.device_id_text)
device_layout.addWidget(copy_button)
layout.addWidget(device_frame)
# 会员状态区域
member_frame = QFrame()
member_frame.setStyleSheet("""
QFrame {
background-color: white;
border-radius: 10px;
padding: 20px;
}
""")
member_layout = QVBoxLayout(member_frame)
member_layout.setContentsMargins(15, 10, 15, 10)
member_layout.setSpacing(8)
# 会员信息显示区域
self.member_info = QTextEdit()
self.member_info.setReadOnly(True)
self.member_info.setFixedHeight(100) # 增加高度
self.member_info.setStyleSheet("""
QTextEdit {
border: none;
background-color: transparent;
font-family: Consolas, Monaco, monospace;
font-size: 14px;
line-height: 1.6;
}
""")
# 设置默认会员信息(简化版)
self.member_info.setText("会员状态: 正常\n到期时间: 2025-02-22 20:44:23")
member_layout.addWidget(self.member_info)
layout.addWidget(member_frame)
# 激活区域
activate_frame = QFrame()
activate_frame.setStyleSheet("""
QFrame {
background-color: white;
border-radius: 10px;
padding: 10px;
}
""")
activate_layout = QVBoxLayout(activate_frame)
activate_layout.setContentsMargins(10, 5, 10, 5)
activate_layout.setSpacing(5)
# 激活标题和说明
activate_title = QLabel("激活(益加)会员,多个激活码可益加整体时长")
activate_title.setStyleSheet("""
QLabel {
color: #333333;
}
""")
# 激活码输入区域
input_layout = QHBoxLayout()
input_layout.setSpacing(10)
self.activate_input = QLineEdit()
self.activate_input.setPlaceholderText("请输入激活码")
activate_button = QPushButton("激活")
activate_button.setMaximumWidth(80)
activate_button.clicked.connect(self.activate_license)
input_layout.addWidget(self.activate_input)
input_layout.addWidget(activate_button)
activate_layout.addWidget(activate_title)
activate_layout.addLayout(input_layout)
layout.addWidget(activate_frame)
# 功能按钮区域
button_frame = QFrame()
button_frame.setStyleSheet("""
QFrame {
background-color: white;
border-radius: 10px;
padding: 15px;
}
""")
button_layout = QVBoxLayout(button_frame)
button_layout.setSpacing(10)
# 第一行按钮
self.update_button = QPushButton("刷新Cursor编辑器授权")
self.update_button.clicked.connect(self.start_update)
# 第二行按钮
self.reset_button = QPushButton("实现Cursor0.45.+限制")
self.reset_button.clicked.connect(self.reset_machine)
# 第三行按钮
self.disable_update_button = QPushButton("禁用Cursor版本更新")
self.disable_update_button.clicked.connect(self.disable_cursor_update)
# 设置按钮样式
for button in [self.update_button, self.reset_button, self.disable_update_button]:
button.setStyleSheet("""
QPushButton {
background-color: #0066cc;
color: white;
border: none;
border-radius: 4px;
padding: 12px;
font-size: 14px;
}
QPushButton:hover {
background-color: #0052a3;
}
QPushButton:pressed {
background-color: #003d7a;
}
""")
button_layout.addWidget(self.update_button)
button_layout.addWidget(self.reset_button)
button_layout.addWidget(self.disable_update_button)
layout.addWidget(button_frame)
# 初始化工作线程
self.worker = None
logging.info("界面元素初始化完成")
except Exception as e:
logging.error(f"初始化界面元素时发生错误: {str(e)}")
raise
def copy_device_id(self):
"""复制设备ID到剪贴板"""
try:
clipboard = QApplication.clipboard()
clipboard.setText(self.device_id_text.text())
QMessageBox.information(self, "提示", "设备ID已复制到剪贴板")
logging.info("设备ID已复制到剪贴板")
except Exception as e:
logging.error(f"复制设备ID时发生错误: {str(e)}")
QMessageBox.warning(self, "错误", f"复制失败: {str(e)}")
def append_log(self, text):
"""添加日志到状态显示区域"""
try:
self.status_text.append(text)
self.status_text.verticalScrollBar().setValue(
self.status_text.verticalScrollBar().maximum()
)
logging.info(f"状态更新: {text}")
except Exception as e:
logging.error(f"更新状态显示时发生错误: {str(e)}")
def start_update(self):
"""开始更新流程"""
try:
self.update_button.setEnabled(False)
self.reset_button.setEnabled(False)
self.status_text.clear()
self.append_log("开始更新流程...")
self.worker = UpdateWorker(self.updater)
self.worker.finished.connect(self.update_finished)
self.worker.progress.connect(self.append_log)
self.worker.start()
logging.info("更新进程已启动")
except Exception as e:
logging.error(f"启动更新进程时发生错误: {str(e)}")
self.update_button.setEnabled(True)
self.reset_button.setEnabled(True)
QMessageBox.warning(self, "错误", f"启动更新失败: {str(e)}")
def update_finished(self, success, message):
"""更新完成的回调"""
try:
self.update_button.setEnabled(True)
self.reset_button.setEnabled(True)
if success:
QMessageBox.information(self, "成功", message)
else:
QMessageBox.warning(self, "失败", message)
self.append_log(message)
logging.info(f"更新完成: {message}")
except Exception as e:
logging.error(f"处理更新完成回调时发生错误: {str(e)}")
def reset_machine(self):
"""重置机器码"""
try:
reply = QMessageBox.question(
self,
"确认",
"确定要重置机器码吗?",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
if reply == QMessageBox.Yes:
logging.info("开始重置机器码")
success = self.updater.reset_machine_id()
if success:
QMessageBox.information(self, "成功", "机器码重置成功")
self.device_id_text.setText(self.updater.hardware_id)
logging.info("机器码重置成功")
else:
QMessageBox.warning(self, "失败", "机器码重置失败")
logging.error("机器码重置失败")
except Exception as e:
logging.error(f"重置机器码时发生错误: {str(e)}")
QMessageBox.warning(self, "错误", f"重置失败: {str(e)}")
def activate_license(self):
"""激活许可证"""
try:
activation_code = self.activate_input.text().strip()
if not activation_code:
QMessageBox.warning(self, "提示", "请输入激活码")
return
# TODO: 实现激活逻辑
logging.info(f"正在处理激活请求,激活码: {activation_code}")
QMessageBox.information(self, "提示", "激活功能即将实现")
except Exception as e:
logging.error(f"激活过程中发生错误: {str(e)}")
QMessageBox.warning(self, "错误", f"激活失败: {str(e)}")
def disable_cursor_update(self):
"""禁用Cursor版本更新"""
try:
# TODO: 实现禁用更新逻辑
logging.info("正在禁用Cursor版本更新...")
QMessageBox.information(self, "提示", "禁用更新功能即将实现")
except Exception as e:
logging.error(f"禁用更新时发生错误: {str(e)}")
QMessageBox.warning(self, "错误", f"禁用失败: {str(e)}")
def main():
try:
logging.info("程序启动...")
app = QApplication(sys.argv)
# 设置应用程序图标
if getattr(sys, 'frozen', False):
# 如果是打包后的应用
application_path = sys._MEIPASS
logging.info(f"运行于打包环境: {application_path}")
else:
# 如果是开发环境
application_path = os.path.dirname(os.path.abspath(__file__))
logging.info(f"运行于开发环境: {application_path}")
# 创建并显示主窗口
window = MainWindow()
window.show()
logging.info("主窗口已显示")
sys.exit(app.exec_())
except Exception as e:
logging.error(f"程序运行时发生错误: {str(e)}")
QMessageBox.critical(None, "错误", f"程序启动失败: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()

View File

@@ -1,4 +1,7 @@
DrissionPage==4.1.0.9
colorama==0.4.6
python-dotenv==1.0.0
pyinstaller
pyinstaller==6.3.0
PyQt5==5.15.9
requests==2.31.0
urllib3==2.1.0

338
update_cursor_token.py Normal file
View File

@@ -0,0 +1,338 @@
import os
import json
import platform
import requests
import urllib3
import ssl
import sys
import subprocess
import hashlib
from cursor_auth_manager import CursorAuthManager
from logger import logging
from reset_machine import MachineIDResetter
import patch_cursor_get_machine_id
from exit_cursor import ExitCursor
import go_cursor_help
from logo import print_logo
from typing import Tuple, Dict, Optional
class CursorTokenUpdater:
def __init__(self):
self.auth_manager = CursorAuthManager()
self._hardware_id = None # 延迟初始化硬件ID
@property
def hardware_id(self) -> str:
"""获取硬件ID延迟初始化"""
if self._hardware_id is None:
self._hardware_id = self._get_hardware_id()
return self._hardware_id
def _get_hardware_id(self) -> str:
"""获取硬件唯一标识
方案1: CPU ID + 主板序列号 + BIOS序列号
方案2: 系统盘序列号 + Windows安装时间
方案3: 计算机名(最后的备选方案)
"""
try:
# 创建startupinfo对象来隐藏命令行窗口
startupinfo = None
if sys.platform == "win32":
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 方案1: 尝试获取硬件信息
try:
# 获取CPU ID
cpu_info = subprocess.check_output('wmic cpu get ProcessorId', startupinfo=startupinfo).decode()
cpu_id = cpu_info.split('\n')[1].strip()
# 获取主板序列号
board_info = subprocess.check_output('wmic baseboard get SerialNumber', startupinfo=startupinfo).decode()
board_id = board_info.split('\n')[1].strip()
# 获取BIOS序列号
bios_info = subprocess.check_output('wmic bios get SerialNumber', startupinfo=startupinfo).decode()
bios_id = bios_info.split('\n')[1].strip()
# 如果所有信息都获取成功且有效
if all([cpu_id, board_id, bios_id]) and not all(x in ['', '0', 'None', 'To be filled by O.E.M.'] for x in [cpu_id, board_id, bios_id]):
combined = f"{cpu_id}:{board_id}:{bios_id}"
hardware_id = hashlib.md5(combined.encode()).hexdigest()
logging.info("使用硬件信息生成ID成功")
return hardware_id
except Exception as e:
logging.warning(f"方案1失败: {str(e)}")
# 方案2: 系统盘序列号 + Windows安装时间
try:
backup_info = []
# 获取系统盘序列号
volume_info = subprocess.check_output('wmic logicaldisk where "DeviceID=\'C:\'" get VolumeSerialNumber', startupinfo=startupinfo).decode()
volume_serial = volume_info.split('\n')[1].strip()
if volume_serial and volume_serial not in ['', '0']:
backup_info.append(("volume", volume_serial))
# 获取Windows安装时间
os_info = subprocess.check_output('wmic os get InstallDate', startupinfo=startupinfo).decode()
install_date = os_info.split('\n')[1].strip()
if install_date:
backup_info.append(("install", install_date))
if backup_info:
combined = "|".join(f"{k}:{v}" for k, v in sorted(backup_info))
hardware_id = hashlib.md5(combined.encode()).hexdigest()
logging.info("使用系统信息生成ID成功")
return hardware_id
except Exception as e:
logging.warning(f"方案2失败: {str(e)}")
# 方案3: 使用计算机名(最后的备选方案)
computer_name = platform.node()
if computer_name:
hardware_id = hashlib.md5(computer_name.encode()).hexdigest()
logging.info("使用计算机名生成ID成功")
return hardware_id
raise ValueError("无法获取任何可用信息来生成硬件ID")
except Exception as e:
error_msg = f"生成硬件ID失败: {str(e)}"
logging.error(error_msg)
raise RuntimeError(error_msg)
def get_unused_account(self) -> Tuple[bool, str, Optional[Dict]]:
"""
从API获取未使用的账号
Returns:
Tuple[bool, str, Optional[Dict]]:
- 是否成功
- 错误信息
- 账号数据(如果成功)
"""
endpoint = "https://cursorapi.nosqli.com/admin/api.account/getUnused"
data = {
"machine_id": self.hardware_id
}
headers = {
"Content-Type": "application/json"
}
# 禁用SSL警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
request_kwargs = {
"json": data,
"headers": headers,
"timeout": 30,
"verify": False
}
try:
try:
response = requests.post(endpoint, **request_kwargs)
except requests.exceptions.SSLError:
# SSL错误时使用自定义SSL上下文
ssl_context = ssl.create_default_context()
ssl_context.check_hostname = False
ssl_context.verify_mode = ssl.CERT_NONE
session = requests.Session()
session.verify = False
response = session.post(endpoint, **request_kwargs)
response_data = response.json()
if response_data.get("code") == 200:
account_data = response_data.get("data", {})
# 获取账号信息
email = account_data.get("email", "")
access_token = account_data.get("access_token", "")
refresh_token = account_data.get("refresh_token", "")
expire_time = account_data.get("expire_time", "")
days_left = account_data.get("days_left", 0)
if not all([email, access_token, refresh_token]):
return False, "获取账号信息不完整", None
account_info = {
"email": email,
"access_token": access_token,
"refresh_token": refresh_token,
"expire_time": expire_time,
"days_left": days_left
}
logging.info(f"成功获取账号信息 - 邮箱: {email}, 剩余天数: {days_left}")
return True, "", account_info
else:
error_msg = response_data.get("msg", "未知错误")
return False, f"API返回错误: {error_msg}", None
except Exception as e:
error_msg = f"获取账号时发生错误: {str(e)}"
logging.error(error_msg)
return False, error_msg, None
def update_auth_info(self, email: str, access_token: str, refresh_token: str = None) -> bool:
"""
更新Cursor的认证信息
Args:
email: 用户邮箱
access_token: 访问令牌
refresh_token: 刷新令牌如果没有提供将使用access_token
Returns:
bool: 更新是否成功
"""
try:
# 如果没有提供refresh_token使用access_token
if refresh_token is None:
refresh_token = access_token
# 更新认证信息
result = self.auth_manager.update_auth(
email=email,
access_token=access_token,
refresh_token=refresh_token
)
if result:
logging.info(f"认证信息更新成功 - 邮箱: {email}")
return True
else:
logging.error("认证信息更新失败")
return False
except Exception as e:
logging.error(f"更新认证信息时发生错误: {str(e)}")
return False
def reset_machine_id(self, greater_than_0_45: bool = True) -> bool:
"""
重置机器码
Args:
greater_than_0_45: 是否大于0.45版本
Returns:
bool: 重置是否成功
"""
try:
logging.info("开始重置机器码...")
resetter = MachineIDResetter()
result = resetter.reset(greater_than_0_45)
if result:
logging.info("机器码重置成功")
# 重置后更新硬件ID缓存
self._hardware_id = None
return True
else:
logging.error("机器码重置失败")
return False
except Exception as e:
logging.error(f"重置机器码时发生错误: {str(e)}")
return False
def patch_machine_id(self) -> bool:
"""
修补机器码获取方法
Returns:
bool: 修补是否成功
"""
try:
logging.info("开始修补机器码获取方法...")
patch_cursor_get_machine_id.patch()
logging.info("机器码获取方法修补完成")
return True
except Exception as e:
logging.error(f"修补机器码获取方法时发生错误: {str(e)}")
return False
def exit_cursor(self) -> bool:
"""
退出Cursor进程
Returns:
bool: 退出是否成功
"""
try:
logging.info("正在退出Cursor进程...")
exit_handler = ExitCursor()
exit_handler.exit()
logging.info("Cursor进程已退出")
return True
except Exception as e:
logging.error(f"退出Cursor进程时发生错误: {str(e)}")
return False
def full_update_process(self, email: str = None, access_token: str = None, refresh_token: str = None) -> bool:
"""
执行完整的更新流程
Args:
email: 用户邮箱可选如果不提供则从API获取
access_token: 访问令牌可选如果不提供则从API获取
refresh_token: 刷新令牌可选如果不提供则从API获取
Returns:
bool: 更新流程是否全部成功
"""
print_logo()
logging.info("=== 开始完整更新流程 ===")
# 1. 退出Cursor进程
if not self.exit_cursor():
return False
# 2. 修补机器码获取方法
if not self.patch_machine_id():
return False
# 3. 重置机器码
if not self.reset_machine_id(greater_than_0_45=True):
return False
# 4. 如果没有提供认证信息从API获取
if not all([email, access_token]):
success, error_msg, account_info = self.get_unused_account()
if not success:
logging.error(f"无法获取账号信息: {error_msg}")
return False
email = account_info["email"]
access_token = account_info["access_token"]
refresh_token = account_info["refresh_token"]
# 5. 更新认证信息
if not self.update_auth_info(email, access_token, refresh_token):
return False
logging.info("=== 所有操作已完成 ===")
return True
def main():
updater = CursorTokenUpdater()
# 从环境变量获取认证信息(可选)
# 如果环境变量中有认证信息,使用环境变量中的信息
# 否则将从API获取新的账号信息
success = updater.full_update_process(
)
print("更新状态:", "成功" if success else "失败")
if __name__ == "__main__":
main()