Compare commits

...

16 Commits

Author SHA1 Message Date
ruisu
58d7dc35b5 feat: 优化激活码输入框,支持已激活状态下叠加时长 2025-02-17 20:55:25 +08:00
ruisu
b50029b673 feat: 添加会员状态定时检测功能 2025-02-17 20:52:37 +08:00
ruisu
cebf87fd3c feat: 添加启动时会员状态检查功能 2025-02-17 20:43:10 +08:00
ruisu
3d9835bd7f feat: 1.1版本更新 - 优化UI界面和激活流程 2025-02-17 20:41:33 +08:00
ruisu
139c73d9c9 改进刷新功能的错误提示: 优化设备未激活的提示信息,提供清晰的解决方案;区分未激活和其他错误的提示方式;引导用户进行正确的激活操作 2025-02-17 20:26:58 +08:00
ruisu
b4b20b71a4 场景分支1.0.1 macm1打包成功 2025-02-17 20:02:56 +08:00
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
17 changed files with 2137 additions and 190 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

73
build_mac_new.py Normal file
View File

@@ -0,0 +1,73 @@
import PyInstaller.__main__
import os
import sys
import shutil
# 获取当前脚本所在目录
current_dir = os.path.dirname(os.path.abspath(__file__))
# 清理之前的构建
dist_dir = os.path.join(current_dir, 'dist')
build_dir = os.path.join(current_dir, 'build')
if os.path.exists(dist_dir):
shutil.rmtree(dist_dir)
if os.path.exists(build_dir):
shutil.rmtree(build_dir)
# 创建必要的目录
os.makedirs('dist', exist_ok=True)
os.makedirs('build', exist_ok=True)
# 创建logs目录
logs_dir = os.path.join(current_dir, 'logs')
os.makedirs(logs_dir, exist_ok=True)
# 打包参数
params = [
'gui/main_mac.py', # 主程序入口
'--name=听泉助手', # 应用名称
'-w', # 不显示控制台窗口
'--clean', # 清理临时文件
'--noconfirm', # 不确认覆盖
'--debug=imports', # 只显示导入相关的调试信息
'--osx-bundle-identifier=com.cursor.pro', # macOS包标识符
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:.', # 添加额外文件 (Mac系统使用:分隔符)
'--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:.',
'--add-data=config.py:.',
'--add-data=browser_utils.py:.',
'--add-data=get_email_code.py:.',
'--hidden-import=PyQt5',
'--hidden-import=PyQt5.QtCore',
'--hidden-import=PyQt5.QtGui',
'--hidden-import=PyQt5.QtWidgets',
'--hidden-import=requests',
'--hidden-import=urllib3',
'--hidden-import=psutil',
'--hidden-import=colorama',
]
# 执行打包
PyInstaller.__main__.run(params)
print("打包完成!应用程序包(.app)已生成在dist目录下。")
# 创建必要的目录和文件
app_path = os.path.join(current_dir, 'dist', 'CursorPro.app', 'Contents', 'MacOS')
if os.path.exists(app_path):
# 创建logs目录
os.makedirs(os.path.join(app_path, 'logs'), exist_ok=True)
# 创建空的error.log文件
with open(os.path.join(app_path, 'error.log'), 'w') as f:
f.write('')
print("已创建必要的目录和文件")

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目录下。")

195
config.py
View File

@@ -1,146 +1,95 @@
from dotenv import load_dotenv
import os
import sys
from logger import logging
import json
import logging
from pathlib import Path
class Config:
"""配置类"""
def __init__(self):
# 获取应用程序的根目录路径
if getattr(sys, "frozen", False):
# 如果是打包后的可执行文件
application_path = os.path.dirname(sys.executable)
else:
# 如果是开发环境
application_path = os.path.dirname(os.path.abspath(__file__))
self.base_url = "https://cursorapi.nosqli.com"
self.api_endpoints = {
"activate": f"{self.base_url}/admin/api.member/activate",
"status": f"{self.base_url}/admin/api.member/status",
"get_unused": f"{self.base_url}/admin/api.account/getUnused",
"heartbeat": f"{self.base_url}/admin/api.account/heartbeat"
}
# 指定 .env 文件的路径
dotenv_path = os.path.join(application_path, ".env")
# macOS配置目录
self.config_dir = Path(os.path.expanduser("~")) / ".cursor_pro"
self.config_file = self.config_dir / "config.json"
self.member_file = self.config_dir / "member.json"
self.load_config()
if not os.path.exists(dotenv_path):
raise FileNotFoundError(f"文件 {dotenv_path} 不存在")
def load_config(self):
"""加载配置"""
try:
self.config_dir.mkdir(parents=True, exist_ok=True)
# 加载 .env 文件
load_dotenv(dotenv_path)
if not self.config_file.exists():
self.save_default_config()
self.imap = False
self.temp_mail = os.getenv("TEMP_MAIL", "").strip().split("@")[0]
self.temp_mail_epin = os.getenv("TEMP_MAIL_EPIN", "").strip()
self.temp_mail_ext = os.getenv("TEMP_MAIL_EXT", "").strip()
self.domain = os.getenv("DOMAIN", "").strip()
with open(self.config_file, "r", encoding="utf-8") as f:
config = json.load(f)
self.api_token = config.get("api_token", "")
# 如果临时邮箱为null则加载IMAP
if self.temp_mail == "null":
self.imap = True
self.imap_server = os.getenv("IMAP_SERVER", "").strip()
self.imap_port = os.getenv("IMAP_PORT", "").strip()
self.imap_user = os.getenv("IMAP_USER", "").strip()
self.imap_pass = os.getenv("IMAP_PASS", "").strip()
self.imap_dir = os.getenv("IMAP_DIR", "inbox").strip()
except Exception as e:
logging.error(f"加载配置失败: {str(e)}")
self.api_token = ""
self.check_config()
def save_member_info(self, info: dict):
"""保存会员信息"""
try:
with open(self.member_file, "w", encoding="utf-8") as f:
json.dump(info, f, indent=2, ensure_ascii=False)
logging.info("会员信息已保存")
except Exception as e:
logging.error(f"保存会员信息失败: {str(e)}")
def get_temp_mail(self):
return self.temp_mail
def get_temp_mail_epin(self):
return self.temp_mail_epin
def get_temp_mail_ext(self):
return self.temp_mail_ext
def get_imap(self):
if not self.imap:
return False
def load_member_info(self) -> dict:
"""读取会员信息"""
try:
if self.member_file.exists():
with open(self.member_file, "r", encoding="utf-8") as f:
info = json.load(f)
logging.info(f"已读取会员信息: 到期时间 {info.get('expire_time', '')}")
return info
except Exception as e:
logging.error(f"读取会员信息失败: {str(e)}")
return {
"imap_server": self.imap_server,
"imap_port": self.imap_port,
"imap_user": self.imap_user,
"imap_pass": self.imap_pass,
"imap_dir": self.imap_dir,
"expire_time": "",
"days": 0,
"new_days": 0
}
def get_domain(self):
return self.domain
def check_config(self):
"""检查配置项是否有效
检查规则:
1. 如果使用 tempmail.plus需要配置 TEMP_MAIL 和 DOMAIN
2. 如果使用 IMAP需要配置 IMAP_SERVER、IMAP_PORT、IMAP_USER、IMAP_PASS
3. IMAP_DIR 是可选的
"""
# 基础配置检查
required_configs = {
"domain": "域名",
def save_default_config(self):
"""保存默认配置"""
config = {
"api_token": ""
}
with open(self.config_file, "w", encoding="utf-8") as f:
json.dump(config, f, indent=2, ensure_ascii=False)
# 检查基础配置
for key, name in required_configs.items():
if not self.check_is_valid(getattr(self, key)):
raise ValueError(f"{name}未配置,请在 .env 文件中设置 {key.upper()}")
# 检查邮箱配置
if self.temp_mail != "null":
# tempmail.plus 模式
if not self.check_is_valid(self.temp_mail):
raise ValueError("临时邮箱未配置,请在 .env 文件中设置 TEMP_MAIL")
else:
# IMAP 模式
imap_configs = {
"imap_server": "IMAP服务器",
"imap_port": "IMAP端口",
"imap_user": "IMAP用户名",
"imap_pass": "IMAP密码",
def save_config(self, api_token: str):
"""保存新的配置"""
config = {
"api_token": api_token
}
with open(self.config_file, "w", encoding="utf-8") as f:
json.dump(config, f, indent=2, ensure_ascii=False)
self.api_token = api_token
logging.info("配置已更新")
for key, name in imap_configs.items():
value = getattr(self, key)
if value == "null" or not self.check_is_valid(value):
raise ValueError(
f"{name}未配置,请在 .env 文件中设置 {key.upper()}"
)
# IMAP_DIR 是可选的,如果设置了就检查其有效性
if self.imap_dir != "null" and not self.check_is_valid(self.imap_dir):
raise ValueError(
"IMAP收件箱目录配置无效请在 .env 文件中正确设置 IMAP_DIR"
)
def check_is_valid(self, value):
"""检查配置项是否有效
def get_api_url(self, endpoint_name: str) -> str:
"""获取API端点URL
Args:
value: 配置项的值
endpoint_name: 端点名称
Returns:
bool: 配置项是否有效
str: 完整的API URL
"""
return isinstance(value, str) and len(str(value).strip()) > 0
def print_config(self):
if self.imap:
logging.info(f"\033[32mIMAP服务器: {self.imap_server}\033[0m")
logging.info(f"\033[32mIMAP端口: {self.imap_port}\033[0m")
logging.info(f"\033[32mIMAP用户名: {self.imap_user}\033[0m")
logging.info(f"\033[32mIMAP密码: {'*' * len(self.imap_pass)}\033[0m")
logging.info(f"\033[32mIMAP收件箱目录: {self.imap_dir}\033[0m")
if self.temp_mail != "null":
logging.info(
f"\033[32m临时邮箱: {self.temp_mail}{self.temp_mail_ext}\033[0m"
)
logging.info(f"\033[32m域名: {self.domain}\033[0m")
# 使用示例
if __name__ == "__main__":
try:
config = Config()
print("环境变量加载成功!")
config.print_config()
except ValueError as e:
print(f"错误: {e}")
url = self.api_endpoints.get(endpoint_name, "")
if not url:
logging.error(f"未找到API端点: {endpoint_name}")
return url

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

538
gui/main_mac.py Normal file
View File

@@ -0,0 +1,538 @@
# -*- coding: utf-8 -*-
import sys
import os
import traceback
from datetime import datetime
def get_app_path():
"""获取应用程序路径"""
if getattr(sys, 'frozen', False):
# 如果是打包后的应用
return os.path.dirname(sys.executable)
else:
# 如果是开发环境
return os.path.dirname(os.path.abspath(__file__))
def setup_logging():
"""设置日志"""
app_path = get_app_path()
log_dir = os.path.join(app_path, 'logs')
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, 'app.log')
error_log = os.path.join(app_path, 'error.log')
# 配置日志
logging.basicConfig(
filename=log_file,
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# 设置错误日志处理
def handle_exception(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
error_msg = ''.join(traceback.format_exception(exc_type, exc_value, exc_traceback))
try:
with open(error_log, 'a') as f:
f.write(f"\n{'-'*60}\n")
f.write(f"Time: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(error_msg)
logging.error(f"Uncaught exception:\n{error_msg}")
except Exception as e:
print(f"Error writing to log file: {str(e)}")
print(error_msg)
sys.excepthook = handle_exception
# 添加父目录到系统路径
current_dir = os.path.dirname(os.path.abspath(__file__))
parent_dir = os.path.dirname(current_dir)
sys.path.append(parent_dir)
try:
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QPushButton, QLabel, QLineEdit, QTextEdit, QMessageBox,
QHBoxLayout, QFrame, QStackedWidget)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer
from PyQt5.QtGui import QFont, QIcon, QPalette, QColor
from update_cursor_token import CursorTokenUpdater
from logger import logging
except Exception as e:
error_path = os.path.join(get_app_path(), 'error.log')
with open(error_path, 'w') as f:
f.write(f"Import Error: {str(e)}\n")
f.write(traceback.format_exc())
sys.exit(1)
# macOS 风格的颜色
MACOS_COLORS = {
'background': '#F5F5F7', # 浅灰色背景
'button': '#0066CC', # 蓝色按钮
'button_hover': '#0052A3', # 深蓝色悬停
'button_pressed': '#003D7A', # 更深的蓝色按下
'text': '#1D1D1F', # 深色文字
'frame': '#FFFFFF', # 白色框架
'input': '#FFFFFF', # 白色输入框
'input_text': '#1D1D1F', # 深色输入文字
'border': '#E5E5E5' # 边框颜色
}
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()
# 检查会员状态
self.check_member_status()
# 启动定时检测
self.setup_status_timer()
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)
# 设置macOS风格的样式
self.setStyleSheet(f"""
QMainWindow {{
background-color: {MACOS_COLORS['background']};
}}
QPushButton {{
background-color: {MACOS_COLORS['button']};
color: white;
border: none;
border-radius: 6px;
padding: 8px 16px;
font-size: 13px;
min-width: 100px;
}}
QPushButton:hover {{
background-color: {MACOS_COLORS['button_hover']};
}}
QPushButton:pressed {{
background-color: {MACOS_COLORS['button_pressed']};
}}
QLabel {{
color: {MACOS_COLORS['text']};
font-size: 13px;
}}
QLabel[title="true"] {{
font-size: 24px;
font-weight: bold;
}}
QTextEdit {{
background-color: {MACOS_COLORS['input']};
color: {MACOS_COLORS['input_text']};
border: 1px solid {MACOS_COLORS['border']};
border-radius: 6px;
padding: 8px;
font-size: 13px;
}}
QLineEdit {{
background-color: {MACOS_COLORS['input']};
color: {MACOS_COLORS['input_text']};
border: 1px solid {MACOS_COLORS['border']};
border-radius: 6px;
padding: 8px;
font-size: 13px;
}}
QFrame {{
background-color: {MACOS_COLORS['frame']};
border: 1px solid {MACOS_COLORS['border']};
border-radius: 8px;
}}
QMessageBox {{
background-color: {MACOS_COLORS['background']};
}}
QMessageBox QPushButton {{
min-width: 80px;
padding: 6px 12px;
}}
""")
# 创建主窗口部件
central_widget = QWidget()
self.setCentralWidget(central_widget)
layout = QVBoxLayout(central_widget)
layout.setSpacing(16)
layout.setContentsMargins(20, 20, 20, 20)
# 标题
title_label = QLabel("听泉助手")
title_label.setProperty("title", "true")
title_label.setAlignment(Qt.AlignCenter)
layout.addWidget(title_label)
# 设备ID显示区域
device_frame = QFrame()
device_layout = QHBoxLayout(device_frame)
device_layout.setContentsMargins(16, 16, 16, 16)
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(100)
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_layout = QVBoxLayout(member_frame)
member_layout.setContentsMargins(16, 16, 16, 16)
member_title = QLabel("会员状态")
member_title.setAlignment(Qt.AlignCenter)
member_layout.addWidget(member_title)
self.member_info = QTextEdit()
self.member_info.setReadOnly(True)
self.member_info.setFixedHeight(80)
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_layout = QVBoxLayout(activate_frame)
activate_layout.setContentsMargins(16, 16, 16, 16)
activate_title = QLabel("激活会员")
activate_title.setAlignment(Qt.AlignCenter)
input_layout = QHBoxLayout()
self.activate_input = QLineEdit()
self.activate_input.setPlaceholderText("请输入激活码")
activate_button = QPushButton("激活")
activate_button.setMaximumWidth(100)
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_layout = QVBoxLayout(button_frame)
button_layout.setSpacing(12)
button_layout.setContentsMargins(16, 16, 16, 16)
self.update_button = QPushButton("刷新授权")
self.update_button.clicked.connect(self.start_update)
self.reset_button = QPushButton("重置机器码")
self.reset_button.clicked.connect(self.reset_machine)
self.disable_update_button = QPushButton("禁用更新")
self.disable_update_button.clicked.connect(self.disable_cursor_update)
button_layout.addWidget(self.update_button)
button_layout.addWidget(self.reset_button)
button_layout.addWidget(self.disable_update_button)
layout.addWidget(button_frame)
def copy_device_id(self):
clipboard = QApplication.clipboard()
clipboard.setText(self.device_id_text.text())
QMessageBox.information(self, "成功", "设备ID已复制到剪贴板")
def append_log(self, text):
self.member_info.append(text)
def start_update(self):
self.update_button.setEnabled(False)
self.worker = UpdateWorker(self.updater)
self.worker.finished.connect(self.update_finished)
self.worker.start()
def update_finished(self, success, message):
self.update_button.setEnabled(True)
if success:
QMessageBox.information(self, "成功", message)
else:
QMessageBox.warning(self, "失败", message)
def reset_machine(self):
try:
self.updater.reset_machine_id()
QMessageBox.information(self, "成功", "机器ID已重置")
except Exception as e:
QMessageBox.critical(self, "错误", f"重置失败: {str(e)}")
def activate_license(self):
"""激活许可证"""
try:
license_key = self.activate_input.text().strip()
if not license_key:
QMessageBox.warning(self, "提示", "请输入激活码")
return
# 禁用激活按钮,防止重复点击
self.activate_input.setEnabled(False)
self.update_button.setEnabled(False)
# 显示处理中的提示
self.member_info.clear()
self.member_info.append("正在激活,请稍候...")
QApplication.processEvents()
# 调用激活接口
success, message, account_info = self.updater.check_activation_code(license_key)
if success:
# 更新界面显示
self.member_info.clear()
self.member_info.append(f"会员状态: 已激活")
self.member_info.append(f"到期时间: {account_info['expire_time']}")
self.member_info.append(f"剩余天数: {account_info['days_left']}")
# 清空激活码输入框并更新提示(不禁用输入框)
self.activate_input.clear()
self.activate_input.setPlaceholderText("输入激活码可叠加时长")
# 启用更新按钮
self.update_button.setEnabled(True)
# 显示成功消息
QMessageBox.information(self, "激活成功",
f"激活成功!\n"
f"到期时间: {account_info['expire_time']}\n"
f"剩余天数: {account_info['days_left']}\n"
f"您可以继续输入其他激活码叠加时长")
logging.info(f"设备激活成功,到期时间: {account_info['expire_time']}")
else:
# 恢复激活输入框
self.activate_input.setPlaceholderText("请输入激活码")
# 更新状态显示
self.member_info.clear()
self.member_info.append("会员状态: 未激活")
self.member_info.append("请输入激活码进行激活")
QMessageBox.warning(self, "激活失败", message)
logging.error(f"激活失败: {message}")
except Exception as e:
# 恢复激活输入框
self.activate_input.setPlaceholderText("请输入激活码")
# 更新状态显示
self.member_info.clear()
self.member_info.append("会员状态: 激活失败")
self.member_info.append("请重新尝试")
logging.error(f"激活过程中发生错误: {str(e)}")
QMessageBox.critical(self, "错误", f"激活过程发生错误: {str(e)}")
finally:
# 根据激活状态设置更新按钮状态
self.update_button.setEnabled(success if 'success' in locals() else False)
def disable_cursor_update(self):
try:
# 这里添加禁用更新逻辑
QMessageBox.information(self, "成功", "已禁用Cursor更新")
except Exception as e:
QMessageBox.critical(self, "错误", f"操作失败: {str(e)}")
def check_member_status(self):
"""检查会员状态"""
try:
logging.info("正在检查会员状态...")
self.member_info.clear()
self.member_info.append("正在检查会员状态...")
QApplication.processEvents()
# 调用API检查状态
success, message, account_info = self.updater.check_member_status()
if success and account_info:
# 更新会员信息显示
self.member_info.clear()
self.member_info.append(f"会员状态: 已激活")
self.member_info.append(f"到期时间: {account_info['expire_time']}")
self.member_info.append(f"剩余天数: {account_info['days_left']}")
# 更新激活输入框提示(不禁用输入框)
self.activate_input.setPlaceholderText("输入激活码可叠加时长")
# 启用更新按钮
self.update_button.setEnabled(True)
logging.info(f"会员状态检查完成 - 到期时间: {account_info['expire_time']}")
else:
# 显示未激活状态
self.member_info.clear()
self.member_info.append("会员状态: 未激活")
self.member_info.append("请输入激活码进行激活")
# 更新激活输入框提示
self.activate_input.setPlaceholderText("请输入激活码")
# 禁用更新按钮
self.update_button.setEnabled(False)
logging.warning("会员状态检查结果:未激活")
if message and "设备未激活" not in message:
QMessageBox.warning(self, "提示", message)
except Exception as e:
self.member_info.clear()
self.member_info.append("会员状态: 检查失败")
self.member_info.append("请稍后重试")
# 启用激活输入框和按钮
self.activate_input.setEnabled(True)
self.activate_input.setPlaceholderText("请输入激活码")
# 禁用更新按钮
self.update_button.setEnabled(False)
logging.error(f"检查会员状态时发生错误: {str(e)}")
QMessageBox.warning(self, "错误", f"检查会员状态失败: {str(e)}")
def setup_status_timer(self):
"""设置定时检测会员状态"""
try:
self.status_timer = QTimer(self)
self.status_timer.timeout.connect(self.silent_check_member_status)
# 设置3分钟检测一次 (180000毫秒)
self.status_timer.start(180000)
logging.info("会员状态定时检测已启动")
except Exception as e:
logging.error(f"设置定时器时发生错误: {str(e)}")
def silent_check_member_status(self):
"""静默检查会员状态"""
try:
logging.info("开始静默检查会员状态...")
success, message, account_info = self.updater.check_member_status()
if success and account_info:
# 更新会员信息显示
self.member_info.clear()
self.member_info.append(f"会员状态: 已激活")
self.member_info.append(f"到期时间: {account_info['expire_time']}")
self.member_info.append(f"剩余天数: {account_info['days_left']}")
# 禁用激活输入框和按钮
self.activate_input.setEnabled(False)
self.activate_input.setPlaceholderText("已激活")
# 启用更新按钮
self.update_button.setEnabled(True)
logging.info(f"静默检查完成 - 会员状态正常,到期时间: {account_info['expire_time']}")
else:
# 显示未激活状态
self.member_info.clear()
self.member_info.append("会员状态: 未激活")
self.member_info.append("请输入激活码进行激活")
# 启用激活输入框和按钮
self.activate_input.setEnabled(True)
self.activate_input.setPlaceholderText("请输入激活码")
# 禁用更新按钮
self.update_button.setEnabled(False)
logging.warning("静默检查结果:会员未激活")
except Exception as e:
logging.error(f"静默检查会员状态时发生错误: {str(e)}")
# 静默检查出错时不显示错误提示,只记录日志
def closeEvent(self, event):
"""窗口关闭事件"""
try:
# 停止定时器
if hasattr(self, 'status_timer'):
self.status_timer.stop()
logging.info("应用程序正常关闭")
event.accept()
except Exception as e:
logging.error(f"关闭窗口时发生错误: {str(e)}")
event.accept()
def main():
try:
# 设置日志
setup_logging()
logging.info("应用程序启动")
app = QApplication(sys.argv)
app.setApplicationName("听泉助手")
app.setOrganizationName("听泉")
app.setOrganizationDomain("cursor.pro")
try:
window = MainWindow()
window.show()
logging.info("主窗口已显示")
return app.exec_()
except Exception as e:
logging.error(f"主窗口创建失败: {str(e)}")
logging.error(traceback.format_exc())
QMessageBox.critical(None, "错误", f"程序启动失败: {str(e)}")
return 1
except Exception as e:
error_path = os.path.join(get_app_path(), 'error.log')
with open(error_path, 'a') as f:
f.write(f"\nApplication Error: {str(e)}\n")
f.write(traceback.format_exc())
return 1
if __name__ == '__main__':
sys.exit(main())

444
gui/main_window.py Normal file
View File

@@ -0,0 +1,444 @@
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:
# 检查是否是未激活错误
if "设备未激活" in message:
QMessageBox.warning(self, "设备未激活",
"请先激活设备后再进行更新。\n"
"您可以:\n"
"1. 输入激活码进行激活\n"
"2. 联系客服获取激活码")
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
# 禁用激活按钮,防止重复点击
self.activate_input.setEnabled(False)
self.update_button.setEnabled(False)
# 显示处理中的提示
QApplication.processEvents()
# 调用激活接口
success, message, account_info = self.updater.check_activation_code(activation_code)
if success:
# 更新界面显示
self.member_info.clear()
self.member_info.append(f"会员状态: 已激活")
self.member_info.append(f"到期时间: {account_info['expire_time']}")
self.member_info.append(f"剩余天数: {account_info['days_left']}")
# 显示成功消息
QMessageBox.information(self, "激活成功",
f"设备已成功激活!\n"
f"到期时间: {account_info['expire_time']}\n"
f"剩余天数: {account_info['days_left']}")
# 清空激活码输入框
self.activate_input.clear()
logging.info(f"设备激活成功,到期时间: {account_info['expire_time']}")
else:
QMessageBox.warning(self, "激活失败", message)
logging.error(f"激活失败: {message}")
except Exception as e:
logging.error(f"激活过程中发生错误: {str(e)}")
QMessageBox.critical(self, "错误", f"激活过程发生错误: {str(e)}")
finally:
# 恢复按钮状态
self.activate_input.setEnabled(True)
self.update_button.setEnabled(True)
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,11 +1,26 @@
# -*- coding: utf-8 -*-
import logging
import os
from datetime import datetime
# Configure logging
log_dir = "logs"
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# 在用户主目录下创建日志目录
home_dir = os.path.expanduser('~')
app_dir = os.path.join(home_dir, '.cursor_pro')
log_dir = os.path.join(app_dir, 'logs')
os.makedirs(log_dir, exist_ok=True)
# 设置日志文件名
log_file = os.path.join(log_dir, f"{datetime.now().strftime('%Y-%m-%d')}.log")
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler(log_file, encoding='utf-8'),
logging.StreamHandler()
]
)
class PrefixFormatter(logging.Formatter):
@@ -17,17 +32,6 @@ class PrefixFormatter(logging.Formatter):
return super().format(record)
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(
os.path.join(log_dir, f"{datetime.now().strftime('%Y-%m-%d')}.log"),
encoding="utf-8",
),
],
)
# 为文件处理器设置自定义格式化器
for handler in logging.getLogger().handlers:
if isinstance(handler, logging.FileHandler):

View File

@@ -40,42 +40,50 @@ def get_cursor_paths() -> Tuple[str, str]:
OSError: 当找不到有效路径或系统不支持时抛出
"""
system = platform.system()
logger.info(f"当前操作系统: {system}")
paths_map = {
"Darwin": {
"base": "/Applications/Cursor.app/Contents/Resources/app",
"package": "package.json",
"main": "out/main.js",
},
"Windows": {
"base": os.path.join(
os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app"
),
"package": "package.json",
"main": "out/main.js",
},
"Linux": {
"bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"],
"package": "package.json",
"main": "out/main.js",
},
}
if system == "Darwin": # macOS
base_path = "/Applications/Cursor.app/Contents/Resources/app"
pkg_path = os.path.join(base_path, "package.json")
main_path = os.path.join(base_path, "out", "main.js")
if system not in paths_map:
raise OSError(f"不支持的操作系统: {system}")
if not os.path.exists(pkg_path) or not os.path.exists(main_path):
raise OSError("在 macOS 系统上未找到 Cursor 安装路径")
return pkg_path, main_path
elif system == "Windows":
base_path = os.path.join(
os.getenv("LOCALAPPDATA", ""),
"Programs",
"Cursor",
"resources",
"app"
)
pkg_path = os.path.join(base_path, "package.json")
main_path = os.path.join(base_path, "out", "main.js")
if not os.path.exists(pkg_path) or not os.path.exists(main_path):
raise OSError("在 Windows 系统上未找到 Cursor 安装路径")
return pkg_path, main_path
elif system == "Linux":
linux_paths = [
"/opt/Cursor/resources/app",
"/usr/share/cursor/resources/app"
]
for base_path in linux_paths:
pkg_path = os.path.join(base_path, "package.json")
main_path = os.path.join(base_path, "out", "main.js")
if os.path.exists(pkg_path) and os.path.exists(main_path):
return pkg_path, main_path
if system == "Linux":
for base in paths_map["Linux"]["bases"]:
pkg_path = os.path.join(base, paths_map["Linux"]["package"])
if os.path.exists(pkg_path):
return (pkg_path, os.path.join(base, paths_map["Linux"]["main"]))
raise OSError("在 Linux 系统上未找到 Cursor 安装路径")
base_path = paths_map[system]["base"]
return (
os.path.join(base_path, paths_map[system]["package"]),
os.path.join(base_path, paths_map[system]["main"]),
)
else:
raise OSError(f"不支持的操作系统: {system}")
def check_system_requirements(pkg_path: str, main_path: str) -> bool:
@@ -298,5 +306,24 @@ def patch_cursor_get_machine_id(restore_mode=False) -> None:
sys.exit(1)
# 添加patch函数作为主函数的别名
def patch(restore_mode=False) -> bool:
"""
patch函数作为patch_cursor_get_machine_id的别名
Args:
restore_mode: 是否为恢复模式
Returns:
bool: 修补是否成功
"""
try:
patch_cursor_get_machine_id(restore_mode)
return True
except Exception as e:
logger.error(f"修补失败: {str(e)}")
return False
if __name__ == "__main__":
patch_cursor_get_machine_id()

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

576
update_cursor_token.py Normal file
View File

@@ -0,0 +1,576 @@
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
import time
import requests.adapters
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:
"""获取硬件唯一标识
macOS: 使用系统序列号和硬件UUID
Windows: CPU ID + 主板序列号 + BIOS序列号
其他: 计算机名(最后的备选方案)
"""
try:
system = platform.system()
if system == "Darwin": # macOS
try:
# 获取系统序列号
serial_number = subprocess.check_output(['system_profiler', 'SPHardwareDataType']).decode()
serial = ""
for line in serial_number.split('\n'):
if 'Serial Number' in line:
serial = line.split(':')[1].strip()
break
# 获取硬件UUID
ioreg_output = subprocess.check_output(['ioreg', '-d2', '-c', 'IOPlatformExpertDevice']).decode()
uuid = ""
for line in ioreg_output.split('\n'):
if 'IOPlatformUUID' in line:
uuid = line.split('=')[1].strip().replace('"', '').replace(' ', '')
break
if serial and uuid:
combined = f"{serial}:{uuid}"
hardware_id = hashlib.md5(combined.encode()).hexdigest()
logging.info("使用macOS硬件信息生成ID成功")
return hardware_id
except Exception as e:
logging.warning(f"获取macOS硬件信息失败: {str(e)}")
elif system == "Windows":
# 创建startupinfo对象来隐藏命令行窗口
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
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("使用Windows硬件信息生成ID成功")
return hardware_id
except Exception as e:
logging.warning(f"获取Windows硬件信息失败: {str(e)}")
# 最后的备选方案:使用计算机名
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("开始重置机器码...")
if greater_than_0_45:
# 对于0.45以上版本使用go_cursor_help
go_cursor_help.go_cursor_help()
logging.info("已调用go_cursor_help重置机器码")
return True
else:
# 对于0.45及以下版本,使用传统方式
resetter = MachineIDResetter()
result = resetter.reset_machine_ids()
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进程...")
result = ExitCursor()
return result
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("=== 开始完整更新流程 ===")
try:
# 1. 退出Cursor进程
if not self.exit_cursor():
logging.error("退出Cursor进程失败")
return False
# 2. 修补机器码获取方法
if not self.patch_machine_id():
logging.error("修补机器码获取方法失败")
return False
# 3. 重置机器码
# 对于0.45以上版本使用go_cursor_help
try:
go_cursor_help.go_cursor_help()
logging.info("已调用go_cursor_help重置机器码")
except Exception as e:
logging.error(f"使用go_cursor_help重置机器码失败: {str(e)}")
# 如果go_cursor_help失败尝试使用传统方式
if not self.reset_machine_id(greater_than_0_45=False):
logging.error("重置机器码失败")
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):
logging.error("更新认证信息失败")
return False
logging.info("=== 所有操作已完成 ===")
return True
except Exception as e:
logging.error(f"更新流程发生错误: {str(e)}")
return False
def _get_network_error_message(self, error: Exception) -> str:
"""获取网络错误的友好提示信息"""
if isinstance(error, requests.exceptions.ConnectTimeout):
return "连接服务器超时,请检查网络连接"
elif isinstance(error, requests.exceptions.ConnectionError):
return "无法连接到服务器,请检查网络连接"
elif isinstance(error, requests.exceptions.ReadTimeout):
return "读取服务器响应超时,请重试"
else:
return str(error)
def get_device_info(self) -> dict:
"""获取设备信息"""
return {
"os": platform.system(),
"os_version": platform.version(),
"machine": platform.machine(),
"hostname": platform.node(),
"hardware_id": self.hardware_id
}
def check_activation_code(self, code: str) -> tuple:
"""检查激活码
Args:
code: 激活码
Returns:
tuple: (成功标志, 消息, 账号信息)
"""
max_retries = 3 # 最大重试次数
retry_delay = 1 # 重试间隔(秒)
for attempt in range(max_retries):
try:
data = {
"machine_id": self.hardware_id,
"code": code
}
# 禁用SSL警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 设置请求参数
request_kwargs = {
"json": data,
"headers": {
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
"Accept": "*/*",
"Connection": "keep-alive"
},
"timeout": 10, # 增加超时时间
"verify": False # 禁用SSL验证
}
# 创建session
session = requests.Session()
session.verify = False
# 设置重试策略
retry_strategy = urllib3.Retry(
total=3, # 总重试次数
backoff_factor=0.5, # 重试间隔
status_forcelist=[500, 502, 503, 504] # 需要重试的HTTP状态码
)
adapter = requests.adapters.HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)
try:
# 尝试发送请求
response = session.post(
"https://cursorapi.nosqli.com/admin/api.member/activate",
**request_kwargs
)
response.raise_for_status() # 检查HTTP状态码
result = response.json()
# 激活成功
if result["code"] == 200:
api_data = result["data"]
# 构造标准的返回数据结构
account_info = {
"status": "active",
"expire_time": api_data.get("expire_time", ""),
"total_days": api_data.get("total_days", 0),
"days_left": api_data.get("days_left", 0),
"device_info": self.get_device_info()
}
return True, result["msg"], account_info
# 激活码无效或已被使用
elif result["code"] == 400:
logging.warning(f"激活码无效或已被使用: {result.get('msg', '未知错误')}")
return False, result.get("msg", "激活码无效或已被使用"), None
# 其他错误情况
else:
error_msg = result.get("msg", "未知错误")
if attempt < max_retries - 1: # 如果还有重试机会
logging.warning(f"{attempt + 1}次尝试失败: {error_msg}, 准备重试...")
time.sleep(retry_delay)
continue
logging.error(f"激活失败: {error_msg}")
return False, error_msg, None
except requests.exceptions.RequestException as e:
if attempt < max_retries - 1: # 如果还有重试机会
logging.warning(f"{attempt + 1}次网络请求失败: {str(e)}, 准备重试...")
time.sleep(retry_delay)
continue
error_msg = self._get_network_error_message(e)
logging.error(f"网络请求失败: {error_msg}")
return False, f"网络连接失败: {error_msg}", None
except Exception as e:
if attempt < max_retries - 1: # 如果还有重试机会
logging.warning(f"{attempt + 1}次请求发生错误: {str(e)}, 准备重试...")
time.sleep(retry_delay)
continue
logging.error(f"激活失败: {str(e)}")
return False, f"激活失败: {str(e)}", None
# 如果所有重试都失败了
return False, "多次尝试后激活失败,请检查网络连接或稍后重试", None
def check_member_status(self) -> Tuple[bool, str, Optional[Dict]]:
"""检查会员状态
Returns:
Tuple[bool, str, Optional[Dict]]:
- 是否成功
- 错误信息
- 账号数据(如果成功)
"""
max_retries = 3
retry_delay = 1
for attempt in range(max_retries):
try:
data = {
"machine_id": self.hardware_id
}
# 禁用SSL警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# 设置请求参数
request_kwargs = {
"json": data,
"headers": {
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
"Accept": "*/*",
"Connection": "keep-alive"
},
"timeout": 10,
"verify": False
}
# 创建session
session = requests.Session()
session.verify = False
# 设置重试策略
retry_strategy = urllib3.Retry(
total=3,
backoff_factor=0.5,
status_forcelist=[500, 502, 503, 504]
)
adapter = requests.adapters.HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)
try:
# 发送请求
response = session.post(
"https://cursorapi.nosqli.com/admin/api.member/status",
**request_kwargs
)
response.raise_for_status()
result = response.json()
if result["code"] == 200:
api_data = result["data"]
account_info = {
"status": "active",
"expire_time": api_data.get("expire_time", ""),
"days_left": api_data.get("days_left", 0),
"device_info": self.get_device_info()
}
return True, "success", account_info
else:
error_msg = result.get("msg", "未知错误")
if attempt < max_retries - 1:
logging.warning(f"{attempt + 1}次检查失败: {error_msg}, 准备重试...")
time.sleep(retry_delay)
continue
return False, error_msg, None
except requests.exceptions.RequestException as e:
if attempt < max_retries - 1:
logging.warning(f"{attempt + 1}次网络请求失败: {str(e)}, 准备重试...")
time.sleep(retry_delay)
continue
error_msg = self._get_network_error_message(e)
return False, f"网络连接失败: {error_msg}", None
except Exception as e:
if attempt < max_retries - 1:
logging.warning(f"{attempt + 1}次请求发生错误: {str(e)}, 准备重试...")
time.sleep(retry_delay)
continue
return False, f"检查失败: {str(e)}", None
return False, "多次尝试后检查失败,请稍后重试", None
def main():
updater = CursorTokenUpdater()
# 从环境变量获取认证信息(可选)
# 如果环境变量中有认证信息,使用环境变量中的信息
# 否则将从API获取新的账号信息
success = updater.full_update_process(
)
print("更新状态:", "成功" if success else "失败")
if __name__ == "__main__":
main()