Compare commits

...

17 Commits

Author SHA1 Message Date
ruisu
4daf592130 优化: 关闭调试日志输出,建议使用终端版本\n\n1. 修改日志级别为WARNING,减少不必要的输出\n2. 终端版本(dist/听泉Cursor助手/听泉Cursor助手)支持完整的密码交互\n3. GUI版本(dist/听泉Cursor助手.app)直接启动时无法sudo交互,不建议使用\n4. 建议客户使用终端版本,可以正常进行密码交互
Some checks failed
Remove old artifacts / remove-old-artifacts (push) Has been cancelled
2025-02-19 21:14:17 +08:00
ruisu
21f85ca6c1 优化: 统一成功提示弹窗样式,优化会员状态检查 2025-02-19 19:16:29 +08:00
ruisu
8bff4ebdf7 feat: 更新第二个按钮文字,使功能更清晰 2025-02-19 18:01:04 +08:00
ruisu
d402ced90d feat: 添加图标生成脚本和Qt配置文件 2025-02-19 17:58:09 +08:00
ruisu
98863b8cb5 fix: 修复日志系统问题,将日志保存到用户目录,修复打包后崩溃问题 2025-02-19 17:56:30 +08:00
ruisu
0b6b3f13aa feat: 完成macOS应用打包功能 2025-02-19 16:29:59 +08:00
ruisu
6a00193333 feat: 添加图形界面和自动化重置功能 - 新增 PyQt6 图形界面,优化密码输入和 sudo 权限处理,改进重置机器码流程,添加应用图标和打包配置,更新依赖项 2025-02-19 16:14:01 +08:00
ruisu
0e4087dd28 feat: 初始化项目,添加 Mac M1 设备唯一ID 生成功能 2025-02-19 15:10:45 +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 2541 additions and 519 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,38 @@
# Cursor Account Manager
适用于 Mac M1 的 Cursor 账号管理工具。
## 功能特性
- 获取设备唯一ID基于硬件信息
- 自动获取账号信息
- 自动更新认证信息
- 自动重置机器码
## 使用方法
1. 安装依赖:
```bash
pip install -r requirements.txt
```
2. 运行程序:
```bash
python3 cursor_account_manager.py
```
## 注意事项
- 仅支持 macOS 系统
- 需要 Python 3.x
- 请确保 Cursor 编辑器已安装
# Cursor Pro 自动化工具使用说明
[English doc](./README.EN.md)
## 交流群 QQ 576045098
## 交流群 QQ 1034718338
## 在线文档
@@ -30,7 +59,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"/>

171
build.py
View File

@@ -1,9 +1,15 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import warnings
import os
import platform
import subprocess
import time
import threading
import json
import sys
from pathlib import Path
# Ignore specific SyntaxWarning
warnings.filterwarnings("ignore", category=SyntaxWarning, module="DrissionPage")
@@ -79,6 +85,169 @@ def filter_output(output):
return "\n".join(important_lines)
def increment_version():
"""增加构建版本号"""
version_file = Path("version.json")
if version_file.exists():
with open(version_file, "r") as f:
version_data = json.load(f)
# 增加构建号
version_data["build"] += 1
# 更新版本号的最后一位
version_parts = version_data["version"].split(".")
version_parts[-1] = str(version_data["build"])
version_data["version"] = ".".join(version_parts)
# 保存更新后的版本信息
with open(version_file, "w") as f:
json.dump(version_data, f, indent=4)
return version_data
else:
print("错误:未找到 version.json 文件")
sys.exit(1)
def create_icns():
"""将 SVG 转换为 ICNS 格式"""
if not Path("icons/logo.svg").exists():
print("错误:未找到 logo.svg 文件")
sys.exit(1)
# 创建临时目录
os.makedirs("icons/tmp.iconset", exist_ok=True)
# 转换 SVG 到 PNG
sizes = [16, 32, 64, 128, 256, 512, 1024]
for size in sizes:
# 普通分辨率
os.system(f"rsvg-convert -w {size} -h {size} icons/logo.svg > icons/tmp.iconset/icon_{size}x{size}.png")
# 高分辨率(@2x
if size <= 512:
os.system(f"rsvg-convert -w {size*2} -h {size*2} icons/logo.svg > icons/tmp.iconset/icon_{size}x{size}@2x.png")
# 生成 icns 文件
os.system("iconutil -c icns icons/tmp.iconset -o icons/logo.icns")
# 清理临时文件
os.system("rm -rf icons/tmp.iconset")
def update_spec(version_data):
"""更新 spec 文件中的版本信息"""
spec_content = f'''# -*- mode: python ; coding: utf-8 -*-
block_cipher = None
a = Analysis(
['cursor_gui.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={{}},
runtime_hooks=[],
excludes=[],
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
noarchive=False,
)
pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='听泉Cursor助手',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
disable_windowed_traceback=False,
argv_emulation=True,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon='icons/logo.icns',
)
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='听泉Cursor助手',
)
app = BUNDLE(
coll,
name='听泉Cursor助手.app',
icon='icons/logo.icns',
bundle_identifier='com.tingquan.cursor',
info_plist={{
'CFBundleShortVersionString': '{version_data["version"]}',
'CFBundleVersion': '{version_data["build"]}',
'NSHighResolutionCapable': 'True',
'LSMinimumSystemVersion': '10.13.0',
'CFBundleName': '听泉Cursor助手',
'CFBundleDisplayName': '听泉Cursor助手',
}},
)'''
with open("cursor_app.spec", "w") as f:
f.write(spec_content)
def build_app():
"""构建应用程序"""
# 增加版本号
version_data = increment_version()
# 创建图标
create_icns()
# 更新 spec 文件
update_spec(version_data)
# 更新主程序中的版本号
update_main_version(version_data["version"])
# 构建应用
os.system("pyinstaller cursor_app.spec")
# 创建压缩包
os.system(f'cd dist && zip -r "听泉Cursor助手_v{version_data["version"]}.zip" "听泉Cursor助手.app"')
print(f"\n构建完成版本号v{version_data['version']}")
print(f"应用程序位置dist/听泉Cursor助手.app")
print(f"压缩包位置dist/听泉Cursor助手_v{version_data['version']}.zip")
def update_main_version(version):
"""更新主程序中的版本号"""
with open("cursor_gui.py", "r") as f:
content = f.read()
# 替换版本号
content = content.replace(
'self.setWindowTitle("Cursor账号管理器 v3.5.3")',
f'self.setWindowTitle("听泉Cursor助手 v{version}")'
)
with open("cursor_gui.py", "w") as f:
f.write(content)
def build():
# Clear screen
os.system("cls" if platform.system().lower() == "windows" else "clear")
@@ -176,4 +345,4 @@ def build():
if __name__ == "__main__":
build()
build_app()

40
create_icns.py Executable file
View File

@@ -0,0 +1,40 @@
#!/usr/bin/env python3
import os
import subprocess
def create_iconset():
# 创建临时iconset目录
if not os.path.exists('icons/tmp.iconset'):
os.makedirs('icons/tmp.iconset')
# 定义需要的图标尺寸
icon_sizes = [16, 32, 64, 128, 256, 512, 1024]
for size in icon_sizes:
# 常规分辨率
output_path = f'icons/tmp.iconset/icon_{size}x{size}.png'
subprocess.run(['sips', '-s', 'format', 'png',
'-z', str(size), str(size),
'icons/logo.svg',
'--out', output_path],
check=True)
# 高分辨率(@2x
if size <= 512:
output_path = f'icons/tmp.iconset/icon_{size}x{size}@2x.png'
subprocess.run(['sips', '-s', 'format', 'png',
'-z', str(size*2), str(size*2),
'icons/logo.svg',
'--out', output_path],
check=True)
# 使用iconutil生成.icns文件
subprocess.run(['iconutil', '-c', 'icns', 'icons/tmp.iconset',
'-o', 'icons/logo.icns'],
check=True)
# 清理临时文件
subprocess.run(['rm', '-rf', 'icons/tmp.iconset'], check=True)
if __name__ == '__main__':
create_iconset()

471
cursor_account_manager.py Normal file
View File

@@ -0,0 +1,471 @@
import os
import platform
import json
import sys
import hashlib
import subprocess
import uuid
import requests
from exit_cursor import ExitCursor
from logger import logging
from cursor_auth_manager import CursorAuthManager
import go_cursor_help
import patch_cursor_get_machine_id
from reset_machine import MachineIDResetter
from logo import print_logo
from typing import Optional, Tuple, Dict, Any
from urllib3.exceptions import InsecureRequestWarning
import urllib3
import time
# 禁用不安全请求警告
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
# 定义 EMOJI 字典
EMOJI = {"ERROR": "", "WARNING": "⚠️", "INFO": ""}
def check_cursor_version():
"""检查cursor版本"""
pkg_path, main_path = patch_cursor_get_machine_id.get_cursor_paths()
with open(pkg_path, "r", encoding="utf-8") as f:
version = json.load(f)["version"]
return patch_cursor_get_machine_id.version_check(version, min_version="0.45.0")
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
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("所有操作已完成")
def get_account_from_api() -> tuple[bool, dict]:
"""从API获取账号信息
Returns:
tuple[bool, dict]: (是否成功, 账号信息)
"""
try:
# 获取设备唯一ID
hardware_id = get_mac_unique_id()
logging.info(f"设备唯一ID: {hardware_id}")
endpoint = "https://cursorapi.nosqli.com/admin/api.account/getUnused"
data = {
"machine_id": hardware_id
}
headers = {
"Content-Type": "application/json"
}
import urllib3
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:
import 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", {})
return True, account_data
else:
error_msg = response_data.get("msg", "未知错误")
logging.error(f"获取未使用账号失败: {error_msg}")
return False, {}
except Exception as e:
logging.error(f"API请求失败: {str(e)}")
return False, {}
except Exception as e:
logging.error(f"获取账号过程出错: {str(e)}")
return False, {}
def get_mac_unique_id() -> str:
"""
获取Mac设备的唯一ID32位MD5
组合以下信息生成唯一标识:
1. 硬件UUID
2. 系统序列号
3. 主板序列号
4. CPU信息
"""
def run_cmd(cmd: str) -> str:
try:
result = subprocess.check_output(cmd, shell=True).decode('utf-8').strip()
return result
except:
return ""
# 收集系统信息
identifiers = []
# 1. 获取硬件UUID
hw_uuid = run_cmd("ioreg -d2 -c IOPlatformExpertDevice | awk -F\\\" '/IOPlatformUUID/{print $(NF-1)}'")
identifiers.append(hw_uuid)
# 2. 获取系统序列号
serial = run_cmd("ioreg -d2 -c IOPlatformExpertDevice | awk -F\\\" '/IOPlatformSerialNumber/{print $(NF-1)}'")
identifiers.append(serial)
# 3. 获取主板信息
board_id = run_cmd("ioreg -d2 -c IOPlatformExpertDevice | awk -F\\\" '/board-id/{print $(NF-1)}'")
identifiers.append(board_id)
# 4. 获取CPU信息
cpu_info = run_cmd("sysctl -n machdep.cpu.brand_string")
identifiers.append(cpu_info)
# 如果以上方法都失败,使用备用方法
if not any(identifiers):
# 使用 UUID 模块获取 UUID不太稳定仅作为备用
identifiers = [
str(uuid.getnode()), # MAC 地址的整数表示
platform.machine(), # CPU 架构
platform.system(), # 操作系统名称
platform.version() # 操作系统版本
]
# 组合所有标识符并生成MD5
unique_string = "".join(filter(None, identifiers))
return hashlib.md5(unique_string.encode()).hexdigest()
class CursorAccountManager:
def __init__(self):
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.member/heartbeat"
}
self.hardware_id = get_mac_unique_id()
def get_device_info(self) -> dict:
"""获取设备信息"""
return {
"system": platform.system(),
"device_name": platform.node(),
"ip": self._get_ip_address(),
"location": self._get_location()
}
def _get_ip_address(self) -> str:
"""获取IP地址"""
try:
response = requests.get('https://api.ipify.org?format=json', timeout=5)
return response.json()['ip']
except:
return "未知"
def _get_location(self) -> str:
"""获取地理位置"""
try:
ip = self._get_ip_address()
if ip != "未知":
response = requests.get(f'http://ip-api.com/json/{ip}', timeout=5)
data = response.json()
if data.get('status') == 'success':
return f"{data.get('country', '')} {data.get('city', '')}"
except:
pass
return "未知"
def check_member_status(self) -> tuple[bool, dict]:
"""检查会员状态
Returns:
tuple[bool, dict]: (是否成功, 状态信息)
"""
try:
data = {
"machine_id": self.hardware_id
}
api_url = self.api_endpoints["status"]
logging.info(f"正在检查会员状态...")
request_kwargs = {
"json": data,
"headers": {"Content-Type": "application/json"},
"timeout": 2,
"verify": False
}
session = requests.Session()
session.verify = False
try:
response = session.post(api_url, **request_kwargs)
except requests.exceptions.Timeout:
logging.warning("首次请求超时,正在重试...")
response = session.post(api_url, **request_kwargs)
result = response.json()
logging.info(f"状态检查响应: {result}")
if result.get("code") in [1, 200]:
api_data = result.get("data", {})
status_data = {
"is_active": api_data.get("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, status_data
else:
error_msg = result.get("msg", "未知错误")
logging.error(f"获取状态失败: {error_msg}")
return False, {
"is_active": False,
"expire_time": "",
"total_days": 0,
"days_left": 0,
"device_info": self.get_device_info()
}
except Exception as e:
logging.error(f"获取会员状态失败: {str(e)}")
return False, {
"is_active": False,
"expire_time": "",
"total_days": 0,
"days_left": 0,
"device_info": self.get_device_info()
}
def check_activation_code(self, code: str) -> tuple[bool, str, dict | None]:
"""检查激活码
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
}
# 设置请求参数
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(
self.api_endpoints["activate"],
**request_kwargs
)
response.raise_for_status()
result = response.json()
logging.info(f"激活响应: {result}")
# 激活成功
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
logging.error(f"网络请求失败: {str(e)}")
return False, f"网络连接失败: {str(e)}", 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 reset_auth_with_password(password: str = None) -> tuple[bool, str]:
"""
封装重置授权的完整流程
Args:
password: 系统密码(可选)
Returns:
tuple[bool, str]: (是否成功, 消息)
"""
try:
logging.info("\n=== 初始化重置流程 ===")
greater_than_0_45 = check_cursor_version()
logging.info("正在从API获取账号信息...")
success, account_data = get_account_from_api()
if not success:
return False, "获取账号信息失败"
email = account_data.get("email", "")
access_token = account_data.get("access_token", "")
refresh_token = account_data.get("refresh_token", "")
if not all([email, access_token, refresh_token]):
return False, "账号信息不完整"
logging.info(f"获取到账号信息:\n邮箱: {email}")
# 更新认证信息
logging.info("正在更新认证信息...")
auth_manager = CursorAuthManager()
if auth_manager.update_auth(email, access_token, refresh_token):
logging.info("认证信息更新成功")
# 重置机器码
logging.info("正在重置机器码...")
# 如果提供了密码,设置环境变量
if password:
import os
os.environ['SUDO_PASSWORD'] = password
reset_machine_id(greater_than_0_45)
logging.info("重置完成")
return True, "重置成功"
else:
return False, "更新认证信息失败"
except Exception as e:
logging.error(f"重置过程出错: {str(e)}")
import traceback
logging.error(traceback.format_exc())
return False, f"重置失败: {str(e)}"
if __name__ == "__main__":
print_logo()
greater_than_0_45 = check_cursor_version()
browser_manager = None
try:
logging.info("\n=== 初始化程序 ===")
# ExitCursor()
logging.info("正在从API获取账号信息...")
success, account_data = get_account_from_api()
if not success:
logging.error("获取账号信息失败")
sys.exit(1)
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]):
logging.error("账号信息不完整")
sys.exit(1)
logging.info(f"获取到账号信息:\n邮箱: {email}\n到期时间: {expire_time}\n剩余天数: {days_left}")
# 更新认证信息
logging.info("正在更新认证信息...")
auth_manager = CursorAuthManager()
if auth_manager.update_auth(email, access_token, refresh_token):
logging.info("认证信息更新成功")
# 重置机器码
logging.info("正在重置机器码...")
reset_machine_id(greater_than_0_45)
logging.info("所有操作已完成")
print_end_message()
else:
logging.error("更新认证信息失败")
except Exception as e:
logging.error(f"程序执行出现错误: {str(e)}")
import traceback
logging.error(traceback.format_exc())
finally:
if browser_manager:
browser_manager.quit()
input("\n程序执行完毕,按回车键退出...")

880
cursor_gui.py Normal file
View File

@@ -0,0 +1,880 @@
import sys
import os
import json
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QLabel, QLineEdit, QPushButton, QTextEdit, QMessageBox,
QHBoxLayout, QDialog)
from PyQt6.QtCore import Qt, QTimer, QPropertyAnimation
from PyQt6.QtGui import QIcon, QPixmap
import traceback
import tempfile
# 创建临时日志文件
temp_log_file = os.path.join(tempfile.gettempdir(), 'cursor_helper_error.log')
def log_error(error_msg):
with open(temp_log_file, 'a', encoding='utf-8') as f:
f.write(f"{error_msg}\n")
try:
from logger import logging
import cursor_account_manager as backend
except Exception as e:
log_error(f"导入模块错误: {str(e)}\n{traceback.format_exc()}")
raise
# 添加密码管理类
class PasswordManager:
def __init__(self):
# 获取应用程序的实际运行路径
if getattr(sys, 'frozen', False):
# 如果是打包后的应用
if sys.platform == 'darwin':
# macOS下使用应用程序包内的Resources目录
bundle_dir = os.path.dirname(sys.executable)
app_root = os.path.abspath(os.path.join(bundle_dir, '..', 'Resources'))
self.app_support_dir = os.path.join(app_root, 'config')
else:
# 其他系统使用可执行文件所在目录
app_root = os.path.dirname(sys.executable)
self.app_support_dir = os.path.join(app_root, 'config')
else:
# 开发环境下使用用户目录
if sys.platform == 'darwin':
self.app_support_dir = os.path.expanduser('~/Library/Application Support/听泉Cursor助手')
else:
self.app_support_dir = os.path.expanduser('~/.听泉Cursor助手')
self.config_file = os.path.join(self.app_support_dir, 'config.json')
os.makedirs(self.app_support_dir, exist_ok=True)
logging.debug(f"密码管理器初始化,配置文件路径: {self.config_file}")
def save_password(self, password):
"""保存密码(这里可以添加简单加密)"""
try:
config = {'password': password}
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(config, f)
logging.debug(f"密码保存{'成功' if password else '已清除'}")
return True
except Exception as e:
logging.error(f"保存密码失败: {str(e)}")
return False
def get_password(self):
"""获取保存的密码"""
try:
if os.path.exists(self.config_file):
with open(self.config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
password = config.get('password')
logging.debug(f"读取到保存的密码: {'' if password else ''}")
return password
except Exception as e:
logging.error(f"读取密码失败: {str(e)}")
return None
class SuccessDialog(QDialog):
def __init__(self, message, parent=None):
super().__init__(parent)
self.setWindowTitle("激活成功")
self.setFixedSize(400, 300)
# 移除可能导致问题的窗口标志
self.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
# 设置样式
self.setStyleSheet("""
QDialog {
background-color: #ffffff;
border: 1px solid #cccccc;
border-radius: 10px;
}
QLabel {
color: #333333;
font-size: 14px;
padding: 10px;
}
QLabel#titleLabel {
font-size: 24px;
font-weight: bold;
color: #0078d4;
}
QLabel#messageLabel {
font-size: 16px;
padding: 20px;
background-color: #f8f8f8;
border-radius: 8px;
}
QPushButton {
background-color: #0078d4;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-size: 16px;
min-width: 120px;
}
QPushButton:hover {
background-color: #006cbd;
}
QPushButton:pressed {
background-color: #005ba1;
}
""")
layout = QVBoxLayout()
layout.setSpacing(20)
layout.setContentsMargins(30, 30, 30, 30)
# 标题
title_label = QLabel("🎉 激活成功")
title_label.setObjectName("titleLabel")
title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(title_label)
# 消息内容
message_label = QLabel(message)
message_label.setObjectName("messageLabel")
message_label.setAlignment(Qt.AlignmentFlag.AlignLeft)
message_label.setWordWrap(True)
layout.addWidget(message_label)
# 确定按钮
ok_button = QPushButton("确定")
ok_button.clicked.connect(self.accept)
ok_button.setCursor(Qt.CursorShape.PointingHandCursor)
# 按钮容器
button_container = QWidget()
button_layout = QHBoxLayout()
button_layout.addStretch()
button_layout.addWidget(ok_button)
button_layout.addStretch()
button_container.setLayout(button_layout)
layout.addWidget(button_container)
self.setLayout(layout)
# 5秒后自动关闭
QTimer.singleShot(5000, self.accept)
# 设置窗口位置为父窗口中心
if parent:
self.move(
parent.x() + (parent.width() - self.width()) // 2,
parent.y() + (parent.height() - self.height()) // 2
)
def showEvent(self, event):
"""窗口显示时的动画效果"""
self.setWindowOpacity(0.0)
self.animation = QPropertyAnimation(self, b"windowOpacity")
self.animation.setDuration(300) # 300ms
self.animation.setStartValue(0.0)
self.animation.setEndValue(1.0)
self.animation.start()
super().showEvent(event)
def closeEvent(self, event):
"""窗口关闭时的动画效果"""
self.animation = QPropertyAnimation(self, b"windowOpacity")
self.animation.setDuration(200) # 200ms
self.animation.setStartValue(1.0)
self.animation.setEndValue(0.0)
self.animation.finished.connect(self.close)
self.animation.start()
event.ignore()
class PasswordDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("输入系统密码")
self.setFixedSize(450, 250) # 增加高度以容纳新控件
# 设置样式
self.setStyleSheet("""
QDialog {
background-color: #ffffff;
border: 1px solid #cccccc;
border-radius: 10px;
}
QLabel {
color: #333333;
font-size: 14px;
padding: 5px;
}
QLabel#tipLabel {
color: #666666;
font-size: 13px;
padding: 12px 15px;
background-color: #f8f8f8;
border-radius: 6px;
line-height: 20px;
min-height: 45px;
}
QLineEdit {
padding: 8px 12px;
border: 2px solid #cccccc;
border-radius: 6px;
background-color: white;
font-size: 14px;
min-height: 20px;
}
QLineEdit:focus {
border-color: #0078d4;
}
QPushButton {
background-color: #0078d4;
color: white;
border: none;
padding: 8px 20px;
border-radius: 6px;
font-size: 14px;
min-width: 80px;
}
QPushButton:hover {
background-color: #006cbd;
}
QPushButton:pressed {
background-color: #005ba1;
}
QPushButton#cancelButton {
background-color: #f0f0f0;
color: #333333;
}
QPushButton#cancelButton:hover {
background-color: #e0e0e0;
}
QPushButton#cancelButton:pressed {
background-color: #d0d0d0;
}
QCheckBox {
color: #333333;
font-size: 13px;
}
QCheckBox::indicator {
width: 18px;
height: 18px;
}
QCheckBox::indicator:unchecked {
border: 2px solid #cccccc;
background: white;
border-radius: 4px;
}
QCheckBox::indicator:checked {
border: 2px solid #0078d4;
background: #0078d4;
border-radius: 4px;
}
""")
layout = QVBoxLayout()
layout.setSpacing(8)
layout.setContentsMargins(20, 20, 20, 20)
# 提示文字
tip_label = QLabel("提示这里需要输入您的Mac电脑开机密码系统管理员密码\n用于执行需要管理员权限的操作。")
tip_label.setObjectName("tipLabel")
tip_label.setWordWrap(True)
layout.addWidget(tip_label)
# 密码输入框
self.password_input = QLineEdit()
self.password_input.setEchoMode(QLineEdit.EchoMode.Password)
self.password_input.setPlaceholderText("请输入Mac系统管理员密码")
layout.addWidget(self.password_input)
# 记住密码选项
from PyQt6.QtWidgets import QCheckBox
self.remember_checkbox = QCheckBox("记住密码")
self.remember_checkbox.setChecked(True) # 默认勾选
layout.addWidget(self.remember_checkbox)
# 按钮区域
button_layout = QHBoxLayout()
button_layout.setSpacing(8)
cancel_button = QPushButton("取消")
cancel_button.setObjectName("cancelButton")
cancel_button.setCursor(Qt.CursorShape.PointingHandCursor)
cancel_button.clicked.connect(self.reject)
cancel_button.setFixedWidth(80)
ok_button = QPushButton("确定")
ok_button.setCursor(Qt.CursorShape.PointingHandCursor)
ok_button.clicked.connect(self.accept)
ok_button.setFixedWidth(80)
button_layout.addStretch()
button_layout.addWidget(cancel_button)
button_layout.addWidget(ok_button)
layout.addSpacing(5)
layout.addLayout(button_layout)
self.setLayout(layout)
# 设置窗口位置为父窗口中心
if parent:
self.move(
parent.x() + (parent.width() - self.width()) // 2,
parent.y() + (parent.height() - self.height()) // 2
)
# 尝试加载保存的密码
self.password_manager = PasswordManager()
saved_password = self.password_manager.get_password()
if saved_password:
self.password_input.setText(saved_password)
def get_password(self):
password = self.password_input.text()
# 如果选择记住密码,则保存
if self.remember_checkbox.isChecked():
self.password_manager.save_password(password)
return password
class CursorGUI(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("听泉Cursor助手 v3.0.2")
self.setFixedSize(600, 600)
# 添加全局会员状态
self.is_member_active = False
# 设置定时器每3分钟检查一次会员状态
self.status_timer = QTimer(self)
self.status_timer.timeout.connect(self.check_member_status)
self.status_timer.start(180000) # 180000毫秒 = 3分钟
# 设置整体样式
self.setStyleSheet("""
QMainWindow {
background-color: #f5f5f5;
}
QWidget {
background-color: #f5f5f5;
color: #333333;
}
QLabel {
color: #333333;
font-size: 14px;
font-weight: bold;
}
QLineEdit {
padding: 8px;
border: 1px solid #cccccc;
border-radius: 4px;
background-color: white;
selection-background-color: #0078d4;
}
QTextEdit {
padding: 8px;
border: 1px solid #cccccc;
border-radius: 4px;
background-color: white;
selection-background-color: #0078d4;
}
QScrollBar:vertical {
border: none;
background: #f0f0f0;
width: 10px;
margin: 0px;
}
QScrollBar::handle:vertical {
background: #c0c0c0;
min-height: 30px;
border-radius: 5px;
}
QScrollBar::handle:vertical:hover {
background: #a0a0a0;
}
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {
height: 0px;
}
QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
background: none;
}
QPushButton {
background-color: #0078d4;
color: white;
padding: 8px;
border: none;
border-radius: 4px;
font-size: 14px;
}
QPushButton:hover {
background-color: #006cbd;
}
QPushButton:pressed {
background-color: #005ba1;
}
""")
# 创建主窗口部件
main_widget = QWidget()
self.setCentralWidget(main_widget)
layout = QVBoxLayout()
layout.setSpacing(15) # 增加垂直间距
layout.setContentsMargins(20, 20, 20, 20) # 设置边距
main_widget.setLayout(layout)
# 设备ID显示区域
id_label = QLabel("设备识别码(勿动):")
id_layout = QHBoxLayout()
id_layout.setSpacing(10) # 设置水平间距
self.id_text = QLineEdit()
self.id_text.setReadOnly(True)
id_copy_btn = QPushButton("复制ID")
id_copy_btn.setFixedWidth(100)
id_copy_btn.clicked.connect(self.copy_device_id)
id_layout.addWidget(self.id_text)
id_layout.addWidget(id_copy_btn)
layout.addWidget(id_label)
layout.addLayout(id_layout)
# 会员状态显示区域
status_label = QLabel("会员状态")
self.status_text = QTextEdit()
self.status_text.setReadOnly(True)
self.status_text.setFixedHeight(150)
layout.addWidget(status_label)
layout.addWidget(self.status_text)
# 激活区域
activate_label = QLabel("激活会员,多个激活码叠加整体时长")
activate_layout = QHBoxLayout()
activate_layout.setSpacing(10) # 设置水平间距
self.activate_input = QLineEdit()
self.activate_input.setPlaceholderText("请输入激活码")
self.activate_btn = QPushButton("激活")
self.activate_btn.setFixedWidth(100)
self.activate_btn.clicked.connect(self.activate_account)
activate_layout.addWidget(self.activate_input)
activate_layout.addWidget(self.activate_btn)
layout.addWidget(activate_label)
layout.addLayout(activate_layout)
# 添加一些间距
layout.addSpacing(20)
# 功能按钮区域
button_style = """
QPushButton {
background-color: #0078d4;
color: white;
padding: 12px;
border: none;
border-radius: 6px;
font-size: 15px;
font-weight: bold;
margin: 5px 0;
}
QPushButton:hover {
background-color: #006cbd;
}
QPushButton:pressed {
background-color: #005ba1;
}
QPushButton:disabled {
background-color: #cccccc;
color: #666666;
}
"""
# 一键重置按钮
self.refresh_btn = QPushButton("刷新Cursor授权 (对话次数用完了提示limit时点一次)")
self.refresh_btn.setStyleSheet(button_style)
self.refresh_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self.refresh_btn.clicked.connect(self.refresh_auth)
layout.addWidget(self.refresh_btn)
# 代用2按钮
self.patch_btn = QPushButton("突破0.45.x限制 (Too many free trials问题点这里)")
self.patch_btn.setStyleSheet(button_style)
self.patch_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self.patch_btn.clicked.connect(self.install_patch)
layout.addWidget(self.patch_btn)
# 代用3按钮
self.update_btn = QPushButton("代用 3")
self.update_btn.setStyleSheet(button_style)
self.update_btn.setCursor(Qt.CursorShape.PointingHandCursor)
self.update_btn.clicked.connect(self.update_cursor)
layout.addWidget(self.update_btn)
# 初始化设备ID和状态
self.update_device_id()
self.update_status()
def update_device_id(self):
"""更新设备ID显示"""
try:
device_id = backend.get_mac_unique_id()
self.id_text.setText(device_id)
except Exception as e:
QMessageBox.warning(self, "错误", f"获取设备ID失败: {str(e)}")
def update_status(self):
"""更新会员状态显示"""
try:
account_manager = backend.CursorAccountManager()
success, status_data = account_manager.check_member_status()
# 获取设备信息
device_info = status_data.get("device_info", {}) if success else {}
# 设置状态文本和更新全局状态
if success and status_data.get("is_active"):
status_emoji = ""
status_text = "正常"
self.is_member_active = True
else:
status_emoji = ""
status_text = "未激活"
self.is_member_active = False
# 格式化显示文本
display_text = f"会员状态:{status_text} {status_emoji}\n"
display_text += f"到期时间:{status_data.get('expire_time', '--') if success else '--'}\n"
display_text += f"总天数:{status_data.get('total_days', 0) if success else 0}\n"
display_text += f"剩余天数:{status_data.get('days_left', 0) if success else 0}\n\n"
# 设备信息部分
display_text += "设备信息:\n"
display_text += f"系统:{device_info.get('system', sys.platform)}\n"
display_text += f"设备名:{device_info.get('device_name', '未知')}\n"
display_text += f"IP地址{device_info.get('ip', '--')}\n"
display_text += f"地理位置:{device_info.get('location', '--')}"
self.status_text.setText(display_text)
except Exception as e:
error_text = "会员状态:未激活 ❌\n"
error_text += "到期时间:--\n"
error_text += "总天数0天\n"
error_text += "剩余天数0天\n\n"
error_text += "设备信息:\n"
error_text += f"系统:{sys.platform}\n"
error_text += f"设备名:{backend.platform.node()}\n"
error_text += "IP地址--\n"
error_text += "地理位置:--"
self.status_text.setText(error_text)
def copy_device_id(self):
"""复制设备ID到剪贴板"""
clipboard = QApplication.clipboard()
clipboard.setText(self.id_text.text())
QMessageBox.information(self, "提示", "设备ID已复制到剪贴板")
def show_success_message(self, message):
"""显示统一的成功提示弹窗"""
msg = QMessageBox(self)
msg.setIcon(QMessageBox.Icon.Information)
msg.setWindowTitle("提示")
msg.setText(message)
msg.setStyleSheet("""
QMessageBox {
background-color: white;
}
QPushButton {
background-color: #0078d4;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
min-width: 80px;
}
QPushButton:hover {
background-color: #006cbd;
}
""")
msg.exec()
def activate_account(self):
"""激活账号"""
code = self.activate_input.text().strip()
if not code:
logging.debug("激活码为空,显示警告")
QMessageBox.warning(self, "警告", "请输入激活码")
return
# 禁用激活按钮,显示加载状态
self.activate_btn.setEnabled(False)
self.activate_btn.setText("激活中...")
QApplication.processEvents()
try:
logging.debug(f"开始检查激活码: {code}")
account_manager = backend.CursorAccountManager()
success, message, account_info = account_manager.check_activation_code(code)
logging.debug(f"激活码检查结果: success={success}, message={message}, account_info={account_info}")
if success:
logging.debug("激活成功,开始更新状态")
# 更新状态显示和全局状态
self.update_status()
self.is_member_active = True
# 构建成功消息
success_message = (
f"🎉 激活成功!\n\n"
f"激活码:{code}\n"
f"到期时间:{account_info.get('expire_time', '--')}\n"
f"总天数:{account_info.get('total_days', 0)}\n"
f"剩余天数:{account_info.get('days_left', 0)}"
)
logging.debug(f"准备显示成功弹窗,消息内容: {success_message}")
# 使用统一的成功提示弹窗
self.show_success_message(success_message)
logging.debug("成功弹窗显示完成")
# 清空输入框
self.activate_input.clear()
logging.debug("激活流程完成")
else:
logging.debug(f"激活失败,显示错误消息: {message}")
QMessageBox.warning(self, "错误", message)
except Exception as e:
error_msg = f"激活过程出错: {str(e)}\n{traceback.format_exc()}"
logging.error(error_msg)
QMessageBox.warning(self, "错误", f"激活过程出错: {str(e)}")
finally:
# 恢复激活按钮状态
logging.debug("恢复激活按钮状态")
self.activate_btn.setEnabled(True)
self.activate_btn.setText("激活")
def show_activation_dialog(self):
"""显示统一的激活提示弹窗"""
msg = QMessageBox(self)
msg.setIcon(QMessageBox.Icon.Warning)
msg.setWindowTitle("提示")
msg.setText("请输入激活码")
# 设置详细信息
msg.setInformativeText("获取会员激活码,请通过以下方式:\n\n" +
"• 官方自助网站cursor.nosqli.com\n" +
"• 微信客服号behikcigxr\n" +
"• 闲鱼店铺xxx\n\n" +
"诚招代理商,欢迎加盟合作!")
# 添加按钮
visit_btn = msg.addButton("复制网站", QMessageBox.ButtonRole.ActionRole)
copy_wx_btn = msg.addButton("复制微信", QMessageBox.ButtonRole.ActionRole)
ok_btn = msg.addButton("确定", QMessageBox.ButtonRole.AcceptRole)
msg.setDefaultButton(ok_btn)
# 设置样式
msg.setStyleSheet("""
QMessageBox {
background-color: white;
}
QPushButton {
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
min-width: 80px;
}
QPushButton[text="确定"] {
background-color: #0078d4;
color: white;
border: none;
}
QPushButton[text="确定"]:hover {
background-color: #006cbd;
}
QPushButton[text="复制网站"], QPushButton[text="复制微信"] {
background-color: white;
border: 1px solid #0078d4;
color: #0078d4;
}
QPushButton[text="复制网站"]:hover, QPushButton[text="复制微信"]:hover {
background-color: #f0f9ff;
}
""")
# 显示对话框
clicked = msg.exec()
# 处理按钮点击
if msg.clickedButton() == visit_btn:
clipboard = QApplication.clipboard()
clipboard.setText("cursor.nosqli.com")
QMessageBox.information(self, "提示", "网址已复制到剪贴板")
elif msg.clickedButton() == copy_wx_btn:
clipboard = QApplication.clipboard()
clipboard.setText("behikcigxr")
QMessageBox.information(self, "提示", "微信号已复制到剪贴板")
return False
def check_member_status(self):
"""检查会员状态"""
try:
account_manager = backend.CursorAccountManager()
success, status_data = account_manager.check_member_status()
if success and status_data.get("is_active"):
self.is_member_active = True
return True
self.is_member_active = False
return False
except Exception as e:
logging.error(f"检查会员状态失败: {str(e)}")
self.is_member_active = False
return False
def refresh_auth(self):
"""刷新授权"""
# 检查会员状态,未激活则显示激活提示
if not self.is_member_active:
self.show_activation_dialog()
return
# 会员已激活,继续执行原有逻辑
while True:
# 先尝试获取保存的密码
password_manager = PasswordManager()
saved_password = password_manager.get_password()
if saved_password:
# 如果有保存的密码,直接使用
password = saved_password
else:
# 如果没有保存的密码,显示密码输入对话框
dialog = PasswordDialog(self)
if dialog.exec() == QDialog.DialogCode.Accepted:
password = dialog.get_password()
else:
QMessageBox.warning(self, "取消", "重置操作已取消")
return
# 显示加载状态
self.refresh_btn.setEnabled(False)
self.refresh_btn.setText("重置中...")
QApplication.processEvents()
try:
success, message = backend.reset_auth_with_password(password)
if success:
self.show_success_message("重置成功")
self.update_status()
break # 成功后退出循环
else:
# 密码错误,清除保存的密码
password_manager.save_password("")
QMessageBox.warning(self, "错误", message)
# 继续循环,重新输入密码
except Exception as e:
QMessageBox.warning(self, "错误", f"重置失败: {str(e)}")
break # 遇到其他错误时退出循环
finally:
# 恢复按钮状态
self.refresh_btn.setEnabled(True)
self.refresh_btn.setText("刷新Cursor授权 (对话次数用完了提示limit时点一次)")
def install_patch(self):
"""安装补丁"""
# 检查会员状态,未激活则显示激活提示
if not self.is_member_active:
self.show_activation_dialog()
return
# 会员已激活,继续执行原有逻辑
try:
# 检查版本
greater_than_0_45 = backend.check_cursor_version()
logging.debug(f"Cursor版本检查结果: {'> 0.45' if greater_than_0_45 else '<= 0.45'}")
while True: # 添加循环以支持重试
# 先尝试获取保存的密码
password_manager = PasswordManager()
saved_password = password_manager.get_password()
if saved_password:
logging.debug("使用保存的密码进行验证")
password = saved_password
else:
logging.debug("没有保存的密码,显示密码输入对话框")
dialog = PasswordDialog(self)
if dialog.exec() == QDialog.DialogCode.Accepted:
password = dialog.get_password()
logging.debug("用户输入了新密码")
else:
logging.debug("用户取消了密码输入")
QMessageBox.warning(self, "取消", "操作已取消")
return
# 显示加载状态
self.patch_btn.setEnabled(False)
self.patch_btn.setText("突破中...")
QApplication.processEvents()
try:
# 设置环境变量
os.environ['SUDO_PASSWORD'] = password
logging.debug("开始执行补丁安装...")
# 执行重置
backend.reset_machine_id(greater_than_0_45)
# 直接显示成功消息
logging.debug("补丁安装完成")
self.show_success_message("补丁安装成功")
self.update_status()
break # 成功后退出循环
except Exception as e:
logging.error(f"补丁安装过程发生错误: {str(e)}")
# 密码错误,清除保存的密码
password_manager.save_password("")
QMessageBox.warning(self, "错误", "密码验证失败,请重新输入")
continue # 继续循环,重新输入密码
finally:
# 恢复按钮状态
self.patch_btn.setEnabled(True)
self.patch_btn.setText("突破0.45.x限制 (Too many free trials问题点这里)")
except Exception as e:
logging.error(f"安装补丁失败: {str(e)}")
QMessageBox.warning(self, "错误", f"安装补丁失败: {str(e)}")
def update_cursor(self):
"""更新Cursor版本"""
# 检查会员状态,未激活则显示激活提示
if not self.is_member_active:
self.show_activation_dialog()
return
# 会员已激活,继续执行原有逻辑
QMessageBox.information(self, "提示", "更新功能开发中")
if __name__ == "__main__":
try:
# 记录启动信息
logging.info("=== 应用程序启动 ===")
logging.info(f"Python 版本: {sys.version}")
logging.info(f"操作系统: {sys.platform}")
app = QApplication(sys.argv)
window = CursorGUI()
window.show()
# 捕获未处理的异常
def handle_exception(exc_type, exc_value, exc_traceback):
error_msg = f"未捕获的异常:\n{traceback.format_exception(exc_type, exc_value, exc_traceback)}"
log_error(error_msg)
logging.error("未捕获的异常:", exc_info=(exc_type, exc_value, exc_traceback))
sys.excepthook = handle_exception
sys.exit(app.exec())
except Exception as e:
error_msg = f"程序启动失败: {str(e)}\n{traceback.format_exc()}"
log_error(error_msg)
logging.error(error_msg)
raise

820
cursor_mac_id_modifier.sh Normal file
View File

@@ -0,0 +1,820 @@
#!/bin/bash
# 设置错误处理
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 日志函数
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_debug() {
echo -e "${BLUE}[DEBUG]${NC} $1"
}
# 获取当前用户
get_current_user() {
if [ "$EUID" -eq 0 ]; then
echo "$SUDO_USER"
else
echo "$USER"
fi
}
CURRENT_USER=$(get_current_user)
if [ -z "$CURRENT_USER" ]; then
log_error "无法获取用户名"
exit 1
fi
# 定义配置文件路径
STORAGE_FILE="$HOME/Library/Application Support/Cursor/User/globalStorage/storage.json"
BACKUP_DIR="$HOME/Library/Application Support/Cursor/User/globalStorage/backups"
# 定义 Cursor 应用程序路径
CURSOR_APP_PATH="/Applications/Cursor.app"
# 检查权限
check_permissions() {
if [ "$EUID" -ne 0 ]; then
log_error "请使用 sudo 运行此脚本"
echo "示例: sudo $0"
exit 1
fi
}
# 检查并关闭 Cursor 进程
check_and_kill_cursor() {
log_info "检查 Cursor 进程..."
local attempt=1
local max_attempts=5
# 函数:获取进程详细信息
get_process_details() {
local process_name="$1"
log_debug "正在获取 $process_name 进程详细信息:"
ps aux | grep -i "$process_name" | grep -v grep
}
while [ $attempt -le $max_attempts ]; do
CURSOR_PIDS=$(pgrep -i "cursor" || true)
if [ -z "$CURSOR_PIDS" ]; then
log_info "未发现运行中的 Cursor 进程"
return 0
fi
log_warn "发现 Cursor 进程正在运行"
get_process_details "cursor"
log_warn "尝试关闭 Cursor 进程..."
if [ $attempt -eq $max_attempts ]; then
log_warn "尝试强制终止进程..."
kill -9 $CURSOR_PIDS 2>/dev/null || true
else
kill $CURSOR_PIDS 2>/dev/null || true
fi
sleep 1
if ! pgrep -i "cursor" > /dev/null; then
log_info "Cursor 进程已成功关闭"
return 0
fi
log_warn "等待进程关闭,尝试 $attempt/$max_attempts..."
((attempt++))
done
log_error "$max_attempts 次尝试后仍无法关闭 Cursor 进程"
get_process_details "cursor"
log_error "请手动关闭进程后重试"
exit 1
}
# 备份系统 ID
backup_system_id() {
log_info "正在备份系统 ID..."
local system_id_file="$BACKUP_DIR/system_id.backup_$(date +%Y%m%d_%H%M%S)"
# 获取并备份 IOPlatformExpertDevice 信息
{
echo "# Original System ID Backup" > "$system_id_file"
echo "## IOPlatformExpertDevice Info:" >> "$system_id_file"
ioreg -rd1 -c IOPlatformExpertDevice >> "$system_id_file"
chmod 444 "$system_id_file"
chown "$CURRENT_USER" "$system_id_file"
log_info "系统 ID 已备份到: $system_id_file"
} || {
log_error "备份系统 ID 失败"
return 1
}
}
# 备份配置文件
backup_config() {
if [ ! -f "$STORAGE_FILE" ]; then
log_warn "配置文件不存在,跳过备份"
return 0
fi
mkdir -p "$BACKUP_DIR"
local backup_file="$BACKUP_DIR/storage.json.backup_$(date +%Y%m%d_%H%M%S)"
if cp "$STORAGE_FILE" "$backup_file"; then
chmod 644 "$backup_file"
chown "$CURRENT_USER" "$backup_file"
log_info "配置已备份到: $backup_file"
else
log_error "备份失败"
exit 1
fi
}
# 生成随机 ID
generate_random_id() {
# 生成32字节(64个十六进制字符)的随机数
openssl rand -hex 32
}
# 生成随机 UUID
generate_uuid() {
uuidgen | tr '[:upper:]' '[:lower:]'
}
# 修改现有文件
modify_or_add_config() {
local key="$1"
local value="$2"
local file="$3"
if [ ! -f "$file" ]; then
log_error "文件不存在: $file"
return 1
fi
# 确保文件可写
chmod 644 "$file" || {
log_error "无法修改文件权限: $file"
return 1
}
# 创建临时文件
local temp_file=$(mktemp)
# 检查key是否存在
if grep -q "\"$key\":" "$file"; then
# key存在,执行替换
sed "s/\"$key\":[[:space:]]*\"[^\"]*\"/\"$key\": \"$value\"/" "$file" > "$temp_file" || {
log_error "修改配置失败: $key"
rm -f "$temp_file"
return 1
}
else
# key不存在,添加新的key-value对
sed "s/}$/,\n \"$key\": \"$value\"\n}/" "$file" > "$temp_file" || {
log_error "添加配置失败: $key"
rm -f "$temp_file"
return 1
}
fi
# 检查临时文件是否为空
if [ ! -s "$temp_file" ]; then
log_error "生成的临时文件为空"
rm -f "$temp_file"
return 1
fi
# 使用 cat 替换原文件内容
cat "$temp_file" > "$file" || {
log_error "无法写入文件: $file"
rm -f "$temp_file"
return 1
}
rm -f "$temp_file"
# 恢复文件权限
chmod 444 "$file"
return 0
}
# 生成新的配置
generate_new_config() {
# 修改系统 ID
log_info "正在修改系统 ID..."
# 备份当前系统 ID
backup_system_id
# 生成新的系统 UUID
local new_system_uuid=$(uuidgen)
# 修改系统 UUID
sudo nvram SystemUUID="$new_system_uuid"
printf "${YELLOW}系统 UUID 已更新为: $new_system_uuid${NC}\n"
printf "${YELLOW}请重启系统以使更改生效${NC}\n"
# 将 auth0|user_ 转换为字节数组的十六进制
local prefix_hex=$(echo -n "auth0|user_" | xxd -p)
local random_part=$(generate_random_id)
local machine_id="${prefix_hex}${random_part}"
local mac_machine_id=$(generate_random_id)
local device_id=$(generate_uuid | tr '[:upper:]' '[:lower:]')
local sqm_id="{$(generate_uuid | tr '[:lower:]' '[:upper:]')}"
log_info "正在修改配置文件..."
# 检查配置文件是否存在
if [ ! -f "$STORAGE_FILE" ]; then
log_error "未找到配置文件: $STORAGE_FILE"
log_warn "请先安装并运行一次 Cursor 后再使用此脚本"
exit 1
fi
# 确保配置文件目录存在
mkdir -p "$(dirname "$STORAGE_FILE")" || {
log_error "无法创建配置目录"
exit 1
}
# 如果文件不存在,创建一个基本的 JSON 结构
if [ ! -s "$STORAGE_FILE" ]; then
echo '{}' > "$STORAGE_FILE" || {
log_error "无法初始化配置文件"
exit 1
}
fi
# 修改现有文件
modify_or_add_config "telemetry.machineId" "$machine_id" "$STORAGE_FILE" || exit 1
modify_or_add_config "telemetry.macMachineId" "$mac_machine_id" "$STORAGE_FILE" || exit 1
modify_or_add_config "telemetry.devDeviceId" "$device_id" "$STORAGE_FILE" || exit 1
modify_or_add_config "telemetry.sqmId" "$sqm_id" "$STORAGE_FILE" || exit 1
# 设置文件权限和所有者
chmod 444 "$STORAGE_FILE" # 改为只读权限
chown "$CURRENT_USER" "$STORAGE_FILE"
# 验证权限设置
if [ -w "$STORAGE_FILE" ]; then
log_warn "无法设置只读权限,尝试使用其他方法..."
chattr +i "$STORAGE_FILE" 2>/dev/null || true
else
log_info "成功设置文件只读权限"
fi
echo
log_info "已更新配置: $STORAGE_FILE"
log_debug "machineId: $machine_id"
log_debug "macMachineId: $mac_machine_id"
log_debug "devDeviceId: $device_id"
log_debug "sqmId: $sqm_id"
}
# 修改 Cursor 主程序文件(安全模式)
modify_cursor_app_files() {
log_info "正在安全修改 Cursor 主程序文件..."
# 验证应用是否存在
if [ ! -d "$CURSOR_APP_PATH" ]; then
log_error "未找到 Cursor.app请确认安装路径: $CURSOR_APP_PATH"
return 1
fi
# 定义目标文件
local target_files=(
"${CURSOR_APP_PATH}/Contents/Resources/app/out/main.js"
"${CURSOR_APP_PATH}/Contents/Resources/app/out/vs/code/node/cliProcessMain.js"
)
# 检查文件是否存在并且是否已修改
local need_modification=false
local missing_files=false
for file in "${target_files[@]}"; do
if [ ! -f "$file" ]; then
log_warn "文件不存在: ${file/$CURSOR_APP_PATH\//}"
missing_files=true
continue
fi
if ! grep -q "return crypto.randomUUID()" "$file" 2>/dev/null; then
log_info "文件需要修改: ${file/$CURSOR_APP_PATH\//}"
need_modification=true
break
else
log_info "文件已修改: ${file/$CURSOR_APP_PATH\//}"
fi
done
# 如果所有文件都已修改或不存在,则退出
if [ "$missing_files" = true ]; then
log_error "部分目标文件不存在,请确认 Cursor 安装是否完整"
return 1
fi
if [ "$need_modification" = false ]; then
log_info "所有目标文件已经被修改过,无需重复操作"
return 0
fi
# 创建临时工作目录
local timestamp=$(date +%Y%m%d_%H%M%S)
local temp_dir="/tmp/cursor_reset_${timestamp}"
local temp_app="${temp_dir}/Cursor.app"
local backup_app="/tmp/Cursor.app.backup_${timestamp}"
# 清理可能存在的旧临时目录
if [ -d "$temp_dir" ]; then
log_info "清理已存在的临时目录..."
rm -rf "$temp_dir"
fi
# 创建新的临时目录
mkdir -p "$temp_dir" || {
log_error "无法创建临时目录: $temp_dir"
return 1
}
# 备份原应用
log_info "备份原应用..."
cp -R "$CURSOR_APP_PATH" "$backup_app" || {
log_error "无法创建应用备份"
rm -rf "$temp_dir"
return 1
}
# 复制应用到临时目录
log_info "创建临时工作副本..."
cp -R "$CURSOR_APP_PATH" "$temp_dir" || {
log_error "无法复制应用到临时目录"
rm -rf "$temp_dir" "$backup_app"
return 1
}
# 确保临时目录的权限正确
chown -R "$CURRENT_USER:staff" "$temp_dir"
chmod -R 755 "$temp_dir"
# 移除签名(增强兼容性)
log_info "移除应用签名..."
codesign --remove-signature "$temp_app" || {
log_warn "移除应用签名失败"
}
# 移除所有相关组件的签名
local components=(
"$temp_app/Contents/Frameworks/Cursor Helper.app"
"$temp_app/Contents/Frameworks/Cursor Helper (GPU).app"
"$temp_app/Contents/Frameworks/Cursor Helper (Plugin).app"
"$temp_app/Contents/Frameworks/Cursor Helper (Renderer).app"
)
for component in "${components[@]}"; do
if [ -e "$component" ]; then
log_info "正在移除签名: $component"
codesign --remove-signature "$component" || {
log_warn "移除组件签名失败: $component"
}
fi
done
# 修改目标文件
local modified_count=0
local files=(
"${temp_app}/Contents/Resources/app/out/main.js"
"${temp_app}/Contents/Resources/app/out/vs/code/node/cliProcessMain.js"
)
for file in "${files[@]}"; do
if [ ! -f "$file" ]; then
log_warn "文件不存在: ${file/$temp_dir\//}"
continue
fi
log_debug "处理文件: ${file/$temp_dir\//}"
# 创建文件备份
cp "$file" "${file}.bak" || {
log_error "无法创建文件备份: ${file/$temp_dir\//}"
continue
}
# 读取文件内容
local content=$(cat "$file")
# 查找 IOPlatformUUID 的位置
local uuid_pos=$(printf "%s" "$content" | grep -b -o "IOPlatformUUID" | cut -d: -f1)
if [ -z "$uuid_pos" ]; then
log_warn "$file 中未找到 IOPlatformUUID"
continue
fi
# 从 UUID 位置向前查找 switch
local before_uuid=${content:0:$uuid_pos}
local switch_pos=$(printf "%s" "$before_uuid" | grep -b -o "switch" | tail -n1 | cut -d: -f1)
if [ -z "$switch_pos" ]; then
log_warn "$file 中未找到 switch 关键字"
continue
fi
# 构建新的文件内容
if printf "%sreturn crypto.randomUUID();\n%s" "${content:0:$switch_pos}" "${content:$switch_pos}" > "$file"; then
((modified_count++))
log_info "成功修改文件: ${file/$temp_dir\//}"
else
log_error "文件写入失败: ${file/$temp_dir\//}"
mv "${file}.bak" "$file"
fi
# 清理备份
rm -f "${file}.bak"
done
if [ "$modified_count" -eq 0 ]; then
log_error "未能成功修改任何文件"
rm -rf "$temp_dir"
return 1
fi
# 重新签名应用(增加重试机制)
local max_retry=3
local retry_count=0
local sign_success=false
while [ $retry_count -lt $max_retry ]; do
((retry_count++))
log_info "尝试签名 (第 $retry_count 次)..."
# 使用更详细的签名参数
if codesign --sign - --force --deep --preserve-metadata=entitlements,identifier,flags "$temp_app" 2>&1 | tee /tmp/codesign.log; then
# 验证签名
if codesign --verify -vvvv "$temp_app" 2>/dev/null; then
sign_success=true
log_info "应用签名验证通过"
break
else
log_warn "签名验证失败,错误日志:"
cat /tmp/codesign.log
fi
else
log_warn "签名失败,错误日志:"
cat /tmp/codesign.log
fi
sleep 1
done
if ! $sign_success; then
log_error "经过 $max_retry 次尝试仍无法完成签名"
log_error "请手动执行以下命令完成签名:"
echo -e "${BLUE}sudo codesign --sign - --force --deep '${temp_app}'${NC}"
echo -e "${YELLOW}操作完成后,请手动将应用复制到原路径:${NC}"
echo -e "${BLUE}sudo cp -R '${temp_app}' '/Applications/'${NC}"
log_info "临时文件保留在:${temp_dir}"
return 1
fi
# 替换原应用
log_info "安装修改版应用..."
if ! sudo rm -rf "$CURSOR_APP_PATH" || ! sudo cp -R "$temp_app" "/Applications/"; then
log_error "应用替换失败,正在恢复..."
sudo rm -rf "$CURSOR_APP_PATH"
sudo cp -R "$backup_app" "$CURSOR_APP_PATH"
rm -rf "$temp_dir" "$backup_app"
return 1
fi
# 清理临时文件
rm -rf "$temp_dir" "$backup_app"
# 设置权限
sudo chown -R "$CURRENT_USER:staff" "$CURSOR_APP_PATH"
sudo chmod -R 755 "$CURSOR_APP_PATH"
log_info "Cursor 主程序文件修改完成!原版备份在: ${backup_app/$HOME/\~}"
return 0
}
# 显示文件树结构
show_file_tree() {
local base_dir=$(dirname "$STORAGE_FILE")
echo
log_info "文件结构:"
echo -e "${BLUE}$base_dir${NC}"
echo "├── globalStorage"
echo "│ ├── storage.json (已修改)"
echo "│ └── backups"
# 列出备份文件
if [ -d "$BACKUP_DIR" ]; then
local backup_files=("$BACKUP_DIR"/*)
if [ ${#backup_files[@]} -gt 0 ]; then
for file in "${backup_files[@]}"; do
if [ -f "$file" ]; then
echo "│ └── $(basename "$file")"
fi
done
else
echo "│ └── (空)"
fi
fi
echo
}
# 显示公众号信息
show_follow_info() {
echo
echo -e "${GREEN}================================${NC}"
echo -e "${YELLOW} 关注公众号【煎饼果子卷AI】一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) ${NC}"
echo -e "${GREEN}================================${NC}"
echo
}
# 禁用自动更新
disable_auto_update() {
local updater_path="$HOME/Library/Application Support/Caches/cursor-updater"
echo
log_info "正在禁用 Cursor 自动更新..."
echo -e "${YELLOW}如果需要恢复自动更新,可以手动删除文件:${NC}"
echo -e "${BLUE}$updater_path${NC}"
echo
# 尝试自动执行
if sudo rm -rf "$updater_path" && \
sudo touch "$updater_path" && \
sudo chmod 444 "$updater_path"; then
log_info "成功禁用自动更新"
echo
log_info "验证方法:"
echo "运行命令ls -l \"$updater_path\""
echo "确认文件权限显示为r--r--r--"
else
log_error "自动设置失败,请手动执行以下命令:"
echo
echo -e "${BLUE}sudo rm -rf \"$updater_path\" && sudo touch \"$updater_path\" && sudo chmod 444 \"$updater_path\"${NC}"
fi
echo
log_info "完成后请重启 Cursor"
}
# 生成随机MAC地址
generate_random_mac() {
# 生成随机MAC地址,保持第一个字节的第二位为0(保证是单播地址)
printf '02:%02x:%02x:%02x:%02x:%02x' $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256))
}
# 获取网络接口列表
get_network_interfaces() {
networksetup -listallhardwareports | awk '/Hardware Port|Ethernet Address/ {print $NF}' | paste - - | grep -v 'N/A'
}
# 备份MAC地址
backup_mac_addresses() {
log_info "正在备份MAC地址..."
local backup_file="$BACKUP_DIR/mac_addresses.backup_$(date +%Y%m%d_%H%M%S)"
{
echo "# Original MAC Addresses Backup - $(date)" > "$backup_file"
echo "## Network Interfaces:" >> "$backup_file"
networksetup -listallhardwareports >> "$backup_file"
chmod 444 "$backup_file"
chown "$CURRENT_USER" "$backup_file"
log_info "MAC地址已备份到: $backup_file"
} || {
log_error "备份MAC地址失败"
return 1
}
}
# 修改MAC地址
modify_mac_address() {
log_info "正在获取网络接口信息..."
# 备份当前MAC地址
backup_mac_addresses
# 获取所有网络接口
local interfaces=$(get_network_interfaces)
if [ -z "$interfaces" ]; then
log_error "未找到可用的网络接口"
return 1
fi
echo
log_info "发现以下网络接口:"
echo "$interfaces" | nl -w2 -s') '
echo
echo -n "请选择要修改的接口编号 (按回车跳过): "
read -r choice
if [ -z "$choice" ]; then
log_info "跳过MAC地址修改"
return 0
fi
# 获取选择的接口名称
local selected_interface=$(echo "$interfaces" | sed -n "${choice}p" | awk '{print $1}')
if [ -z "$selected_interface" ]; then
log_error "无效的选择"
return 1
fi
# 生成新的MAC地址
local new_mac=$(generate_random_mac)
log_info "正在修改接口 $selected_interface 的MAC地址..."
# 关闭网络接口
sudo ifconfig "$selected_interface" down || {
log_error "无法关闭网络接口"
return 1
}
# 修改MAC地址
if sudo ifconfig "$selected_interface" ether "$new_mac"; then
# 重新启用网络接口
sudo ifconfig "$selected_interface" up
log_info "成功修改MAC地址为: $new_mac"
echo
log_warn "请注意: MAC地址修改可能需要重新连接网络才能生效"
else
log_error "修改MAC地址失败"
# 尝试恢复网络接口
sudo ifconfig "$selected_interface" up
return 1
fi
}
# 新增恢复功能选项
restore_feature() {
# 检查备份目录是否存在
if [ ! -d "$BACKUP_DIR" ]; then
log_warn "备份目录不存在"
return 1
fi
# 使用 find 命令获取备份文件列表并存储到数组
backup_files=()
while IFS= read -r file; do
[ -f "$file" ] && backup_files+=("$file")
done < <(find "$BACKUP_DIR" -name "*.backup_*" -type f 2>/dev/null | sort)
# 检查是否找到备份文件
if [ ${#backup_files[@]} -eq 0 ]; then
log_warn "未找到任何备份文件"
return 1
fi
echo
log_info "可用的备份文件:"
echo "0) 退出 (默认)"
# 显示备份文件列表
for i in "${!backup_files[@]}"; do
echo "$((i+1))) $(basename "${backup_files[$i]}")"
done
echo
echo -n "请选择要恢复的备份文件编号 [0-${#backup_files[@]}] (默认: 0): "
read -r choice
# 处理用户输入
if [ -z "$choice" ] || [ "$choice" = "0" ]; then
log_info "跳过恢复操作"
return 0
fi
# 验证输入
if ! [[ "$choice" =~ ^[0-9]+$ ]] || [ "$choice" -gt "${#backup_files[@]}" ]; then
log_error "无效的选择"
return 1
fi
# 获取选择的备份文件
local selected_backup="${backup_files[$((choice-1))]}"
# 验证文件存在性和可读性
if [ ! -f "$selected_backup" ] || [ ! -r "$selected_backup" ]; then
log_error "无法访问选择的备份文件"
return 1
fi
# 尝试恢复配置
if cp "$selected_backup" "$STORAGE_FILE"; then
chmod 644 "$STORAGE_FILE"
chown "$CURRENT_USER" "$STORAGE_FILE"
log_info "已从备份文件恢复配置: $(basename "$selected_backup")"
return 0
else
log_error "恢复配置失败"
return 1
fi
}
# 主函数
main() {
# 新增环境检查
if [[ $(uname) != "Darwin" ]]; then
log_error "本脚本仅支持 macOS 系统"
exit 1
fi
clear
# 显示 Logo
echo -e "
██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗
██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝
██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝
"
echo -e "${BLUE}================================${NC}"
echo -e "${GREEN} Cursor 设备ID 修改工具 ${NC}"
echo -e "${YELLOW} 关注公众号【煎饼果子卷AI】 ${NC}"
echo -e "${YELLOW} 一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) ${NC}"
echo -e "${BLUE}================================${NC}"
echo
echo -e "${YELLOW}[重要提示]${NC} 本工具支持 Cursor v0.45.x"
echo -e "${YELLOW}[重要提示]${NC} 本工具免费如果对您有帮助请关注公众号【煎饼果子卷AI】"
echo
check_permissions
check_and_kill_cursor
backup_config
generate_new_config
modify_cursor_app_files
# 添加MAC地址修改选项
echo
log_warn "是否要修改MAC地址"
echo "0) 否 - 保持默认设置 (默认)"
echo "1) 是 - 修改MAC地址"
echo -n "请输入选择 [0-1] (默认 0): "
read -r choice
# 处理用户输入(包括空输入和无效输入)
case "$choice" in
1)
if modify_mac_address; then
log_info "MAC地址修改完成"
else
log_error "MAC地址修改失败"
fi
;;
*)
log_info "已跳过MAC地址修改"
;;
esac
show_file_tree
show_follow_info
# 直接执行禁用自动更新
disable_auto_update
log_info "请重启 Cursor 以应用新的配置"
# 新增恢复功能选项
#restore_feature
# 显示最后的提示信息
show_follow_info
}
# 执行主函数
main

View File

@@ -1,478 +0,0 @@
import os
import platform
import json
import sys
from colorama import Fore, Style
from enum import Enum
from typing import Optional
from exit_cursor import ExitCursor
import patch_cursor_get_machine_id
from reset_machine import MachineIDResetter
os.environ["PYTHONVERBOSE"] = "0"
os.environ["PYINSTALLER_VERBOSE"] = "0"
import time
import random
from cursor_auth_manager import CursorAuthManager
import os
from logger import logging
from browser_utils import BrowserManager
from get_email_code import EmailVerificationHandler
from logo import print_logo
from config import Config
from datetime import datetime
# 定义 EMOJI 字典
EMOJI = {"ERROR": "", "WARNING": "⚠️", "INFO": ""}
class VerificationStatus(Enum):
"""验证状态枚举"""
PASSWORD_PAGE = "@name=password"
CAPTCHA_PAGE = "@data-index=0"
ACCOUNT_SETTINGS = "Account Settings"
class TurnstileError(Exception):
"""Turnstile 验证相关异常"""
pass
def save_screenshot(tab, stage: str, timestamp: bool = True) -> None:
"""
保存页面截图
Args:
tab: 浏览器标签页对象
stage: 截图阶段标识
timestamp: 是否添加时间戳
"""
try:
# 创建 screenshots 目录
screenshot_dir = "screenshots"
if not os.path.exists(screenshot_dir):
os.makedirs(screenshot_dir)
# 生成文件名
if timestamp:
filename = f"turnstile_{stage}_{int(time.time())}.png"
else:
filename = f"turnstile_{stage}.png"
filepath = os.path.join(screenshot_dir, filename)
# 保存截图
tab.get_screenshot(filepath)
logging.debug(f"截图已保存: {filepath}")
except Exception as e:
logging.warning(f"截图保存失败: {str(e)}")
def check_verification_success(tab) -> Optional[VerificationStatus]:
"""
检查验证是否成功
Returns:
VerificationStatus: 验证成功时返回对应状态,失败返回 None
"""
for status in VerificationStatus:
if tab.ele(status.value):
logging.info(f"验证成功 - 已到达{status.name}页面")
return status
return None
def handle_turnstile(tab, max_retries: int = 2, retry_interval: tuple = (1, 2)) -> bool:
"""
处理 Turnstile 验证
Args:
tab: 浏览器标签页对象
max_retries: 最大重试次数
retry_interval: 重试间隔时间范围(最小值, 最大值)
Returns:
bool: 验证是否成功
Raises:
TurnstileError: 验证过程中出现异常
"""
logging.info("正在检测 Turnstile 验证...")
save_screenshot(tab, "start")
retry_count = 0
try:
while retry_count < max_retries:
retry_count += 1
logging.debug(f"{retry_count} 次尝试验证")
try:
# 定位验证框元素
challenge_check = (
tab.ele("@id=cf-turnstile", timeout=2)
.child()
.shadow_root.ele("tag:iframe")
.ele("tag:body")
.sr("tag:input")
)
if challenge_check:
logging.info("检测到 Turnstile 验证框,开始处理...")
# 随机延时后点击验证
time.sleep(random.uniform(1, 3))
challenge_check.click()
time.sleep(2)
# 保存验证后的截图
save_screenshot(tab, "clicked")
# 检查验证结果
if check_verification_success(tab):
logging.info("Turnstile 验证通过")
save_screenshot(tab, "success")
return True
except Exception as e:
logging.debug(f"当前尝试未成功: {str(e)}")
# 检查是否已经验证成功
if check_verification_success(tab):
return True
# 随机延时后继续下一次尝试
time.sleep(random.uniform(*retry_interval))
# 超出最大重试次数
logging.error(f"验证失败 - 已达到最大重试次数 {max_retries}")
save_screenshot(tab, "failed")
return False
except Exception as e:
error_msg = f"Turnstile 验证过程发生异常: {str(e)}"
logging.error(error_msg)
save_screenshot(tab, "error")
raise TurnstileError(error_msg)
def get_cursor_session_token(tab, max_attempts=3, retry_interval=2):
"""
获取Cursor会话token带有重试机制
:param tab: 浏览器标签页
:param max_attempts: 最大尝试次数
:param retry_interval: 重试间隔(秒)
:return: session token 或 None
"""
logging.info("开始获取cookie")
attempts = 0
while attempts < max_attempts:
try:
cookies = tab.cookies()
for cookie in cookies:
if cookie.get("name") == "WorkosCursorSessionToken":
return cookie["value"].split("%3A%3A")[1]
attempts += 1
if attempts < max_attempts:
logging.warning(
f"{attempts} 次尝试未获取到CursorSessionToken{retry_interval}秒后重试..."
)
time.sleep(retry_interval)
else:
logging.error(
f"已达到最大尝试次数({max_attempts})获取CursorSessionToken失败"
)
except Exception as e:
logging.error(f"获取cookie失败: {str(e)}")
attempts += 1
if attempts < max_attempts:
logging.info(f"将在 {retry_interval} 秒后重试...")
time.sleep(retry_interval)
return None
def update_cursor_auth(email=None, access_token=None, refresh_token=None):
"""
更新Cursor的认证信息的便捷函数
"""
auth_manager = CursorAuthManager()
return auth_manager.update_auth(email, access_token, refresh_token)
def sign_up_account(browser, tab):
logging.info("=== 开始注册账号流程 ===")
logging.info(f"正在访问注册页面: {sign_up_url}")
tab.get(sign_up_url)
try:
if tab.ele("@name=first_name"):
logging.info("正在填写个人信息...")
tab.actions.click("@name=first_name").input(first_name)
logging.info(f"已输入名字: {first_name}")
time.sleep(random.uniform(1, 3))
tab.actions.click("@name=last_name").input(last_name)
logging.info(f"已输入姓氏: {last_name}")
time.sleep(random.uniform(1, 3))
tab.actions.click("@name=email").input(account)
logging.info(f"已输入邮箱: {account}")
time.sleep(random.uniform(1, 3))
logging.info("提交个人信息...")
tab.actions.click("@type=submit")
except Exception as e:
logging.error(f"注册页面访问失败: {str(e)}")
return False
handle_turnstile(tab)
try:
if tab.ele("@name=password"):
logging.info("正在设置密码...")
tab.ele("@name=password").input(password)
time.sleep(random.uniform(1, 3))
logging.info("提交密码...")
tab.ele("@type=submit").click()
logging.info("密码设置完成,等待系统响应...")
except Exception as e:
logging.error(f"密码设置失败: {str(e)}")
return False
if tab.ele("This email is not available."):
logging.error("注册失败:邮箱已被使用")
return False
handle_turnstile(tab)
while True:
try:
if tab.ele("Account Settings"):
logging.info("注册成功 - 已进入账户设置页面")
break
if tab.ele("@data-index=0"):
logging.info("正在获取邮箱验证码...")
code = email_handler.get_verification_code()
if not code:
logging.error("获取验证码失败")
return False
logging.info(f"成功获取验证码: {code}")
logging.info("正在输入验证码...")
i = 0
for digit in code:
tab.ele(f"@data-index={i}").input(digit)
time.sleep(random.uniform(0.1, 0.3))
i += 1
logging.info("验证码输入完成")
break
except Exception as e:
logging.error(f"验证码处理过程出错: {str(e)}")
handle_turnstile(tab)
wait_time = random.randint(3, 6)
for i in range(wait_time):
logging.info(f"等待系统处理中... 剩余 {wait_time-i}")
time.sleep(1)
logging.info("正在获取账户信息...")
tab.get(settings_url)
try:
usage_selector = (
"css:div.col-span-2 > div > div > div > div > "
"div:nth-child(1) > div.flex.items-center.justify-between.gap-2 > "
"span.font-mono.text-sm\\/\\[0\\.875rem\\]"
)
usage_ele = tab.ele(usage_selector)
if usage_ele:
usage_info = usage_ele.text
total_usage = usage_info.split("/")[-1].strip()
logging.info(f"账户可用额度上限: {total_usage}")
except Exception as e:
logging.error(f"获取账户额度信息失败: {str(e)}")
logging.info("\n=== 注册完成 ===")
account_info = f"Cursor 账号信息:\n邮箱: {account}\n密码: {password}"
logging.info(account_info)
time.sleep(5)
return True
class EmailGenerator:
def __init__(
self,
password="".join(
random.choices(
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*",
k=12,
)
),
):
configInstance = Config()
configInstance.print_config()
self.domain = configInstance.get_domain()
self.default_password = password
self.default_first_name = self.generate_random_name()
self.default_last_name = self.generate_random_name()
def generate_random_name(self, length=6):
"""生成随机用户名"""
first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
rest_letters = "".join(
random.choices("abcdefghijklmnopqrstuvwxyz", k=length - 1)
)
return first_letter + rest_letters
def generate_email(self, length=8):
"""生成随机邮箱地址"""
random_str = "".join(random.choices("abcdefghijklmnopqrstuvwxyz", k=length))
timestamp = str(int(time.time()))[-6:] # 使用时间戳后6位
return f"{random_str}{timestamp}@{self.domain}"
def get_account_info(self):
"""获取完整的账号信息"""
return {
"email": self.generate_email(),
"password": self.default_password,
"first_name": self.default_first_name,
"last_name": self.default_last_name,
}
def get_user_agent():
"""获取user_agent"""
try:
# 使用JavaScript获取user agent
browser_manager = BrowserManager()
browser = browser_manager.init_browser()
user_agent = browser.latest_tab.run_js("return navigator.userAgent")
browser_manager.quit()
return user_agent
except Exception as e:
logging.error(f"获取user agent失败: {str(e)}")
return None
def check_cursor_version():
"""检查cursor版本"""
pkg_path, main_path = patch_cursor_get_machine_id.get_cursor_paths()
with open(pkg_path, "r", encoding="utf-8") as f:
version = json.load(f)["version"]
return patch_cursor_get_machine_id.version_check(version, min_version="0.45.0")
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()
else:
MachineIDResetter().reset_machine_ids()
if __name__ == "__main__":
print_logo()
greater_than_0_45 = check_cursor_version()
browser_manager = None
try:
logging.info("\n=== 初始化程序 ===")
# ExitCursor()
# 提示用户选择操作模式
print("\n请选择操作模式:")
print("1. 仅重置机器码")
print("2. 完整注册流程")
while True:
try:
choice = int(input("请输入选项 (1 或 2): ").strip())
if choice in [1, 2]:
break
else:
print("无效的选项,请重新输入")
except ValueError:
print("请输入有效的数字")
if choice == 1:
# 仅执行重置机器码
reset_machine_id(greater_than_0_45)
logging.info("机器码重置完成")
sys.exit(0)
logging.info("正在初始化浏览器...")
# 获取user_agent
user_agent = get_user_agent()
if not user_agent:
logging.error("获取user agent失败使用默认值")
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
# 剔除user_agent中的"HeadlessChrome"
user_agent = user_agent.replace("HeadlessChrome", "Chrome")
browser_manager = BrowserManager()
browser = browser_manager.init_browser(user_agent)
# 获取并打印浏览器的user-agent
user_agent = browser.latest_tab.run_js("return navigator.userAgent")
logging.info("正在初始化邮箱验证模块...")
email_handler = EmailVerificationHandler()
logging.info("\n=== 配置信息 ===")
login_url = "https://authenticator.cursor.sh"
sign_up_url = "https://authenticator.cursor.sh/sign-up"
settings_url = "https://www.cursor.com/settings"
mail_url = "https://tempmail.plus"
logging.info("正在生成随机账号信息...")
email_generator = EmailGenerator()
account = email_generator.generate_email()
password = email_generator.default_password
first_name = email_generator.default_first_name
last_name = email_generator.default_last_name
logging.info(f"生成的邮箱账号: {account}")
auto_update_cursor_auth = True
tab = browser.latest_tab
tab.run_js("try { turnstile.reset() } catch(e) { }")
logging.info("\n=== 开始注册流程 ===")
logging.info(f"正在访问登录页面: {login_url}")
tab.get(login_url)
if sign_up_account(browser, tab):
logging.info("正在获取会话令牌...")
token = get_cursor_session_token(tab)
if token:
logging.info("更新认证信息...")
update_cursor_auth(
email=account, access_token=token, refresh_token=token
)
logging.info("重置机器码...")
reset_machine_id(greater_than_0_45)
logging.info("所有操作已完成")
else:
logging.error("获取会话令牌失败,注册流程未完成")
except Exception as e:
logging.error(f"程序执行出现错误: {str(e)}")
import traceback
logging.error(traceback.format_exc())
finally:
if browser_manager:
browser_manager.quit()
input("\n程序执行完毕,按回车键退出...")

39
go_cursor_help.py Normal file
View File

@@ -0,0 +1,39 @@
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
# 从环境变量获取密码
sudo_password = os.environ.get('SUDO_PASSWORD')
if sudo_password:
# 使用echo传递密码给sudo
cmd = f'echo "{sudo_password}" | sudo -S bash -c \'curl -fsSL {base_url}/cursor_mac_id_modifier.sh | bash\''
else:
cmd = f'curl -fsSL {base_url}/cursor_mac_id_modifier.sh | sudo bash'
logging.info("执行macOS命令")
result = os.system(cmd)
if result != 0:
raise Exception("执行命令失败,请检查密码是否正确")
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

5
icons/app.svg Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<rect width="1024" height="1024" rx="200" fill="#0078d4"/>
<text x="512" y="640" font-family="Arial" font-size="600" text-anchor="middle" fill="white">C</text>
</svg>

After

Width:  |  Height:  |  Size: 318 B

BIN
icons/logo.icns Normal file

Binary file not shown.

14
icons/logo.svg Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="1024" height="1024" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
<!-- 背景 -->
<rect width="1024" height="1024" rx="200" fill="#1e88e5"/>
<!-- 水波纹效果 -->
<path d="M312,512 Q512,412 712,512" stroke="white" stroke-width="20" fill="none" opacity="0.6"/>
<path d="M312,562 Q512,462 712,562" stroke="white" stroke-width="15" fill="none" opacity="0.4"/>
<path d="M312,612 Q512,512 712,612" stroke="white" stroke-width="10" fill="none" opacity="0.2"/>
<!-- 主文字 -->
<text x="512" y="480" font-family="Arial" font-size="180" font-weight="bold" text-anchor="middle" fill="white">听泉</text>
<text x="512" y="680" font-family="Arial" font-size="120" font-weight="bold" text-anchor="middle" fill="white">Cursor</text>
</svg>

After

Width:  |  Height:  |  Size: 838 B

View File

@@ -1,52 +1,61 @@
import logging
import os
import sys
from datetime import datetime
# Configure logging
log_dir = "logs"
if not os.path.exists(log_dir):
os.makedirs(log_dir)
class PrefixFormatter(logging.Formatter):
"""自定义格式化器,为 DEBUG 级别日志添加开源项目前缀"""
def format(self, record):
if record.levelno == logging.DEBUG: # 只给 DEBUG 级别添加前缀
record.msg = f"[开源项目https://github.com/chengazhen/cursor-auto-free] {record.msg}"
if record.levelno == 10: # DEBUG 级别的数值是 10
record.msg = f"[听泉助手https://cursorapi.nosqli.com] {record.msg}"
return super().format(record)
def setup_logger():
"""设置日志系统"""
# 获取用户的应用程序支持目录
if sys.platform == 'darwin':
app_support_dir = os.path.expanduser('~/Library/Application Support/听泉Cursor助手')
else:
app_support_dir = os.path.expanduser('~/.听泉Cursor助手')
# 创建应用程序支持目录
os.makedirs(app_support_dir, exist_ok=True)
# 创建日志目录
log_dir = os.path.join(app_support_dir, 'logs')
os.makedirs(log_dir, exist_ok=True)
# 设置日志文件路径
log_file = os.path.join(log_dir, f"{datetime.now().strftime('%Y-%m-%d')}.log")
# 创建日志记录器
logger = logging.getLogger('CursorHelper')
logger.setLevel(logging.WARNING) # 修改为WARNING级别
# 创建文件处理器
file_handler = logging.FileHandler(log_file, encoding='utf-8')
file_handler.setLevel(logging.WARNING) # 文件处理器也设置为WARNING级别
file_handler.setFormatter(PrefixFormatter('%(asctime)s - %(levelname)s - %(message)s'))
# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.WARNING) # 控制台也设置为WARNING级别
console_handler.setFormatter(PrefixFormatter('%(levelname)s: %(message)s'))
# 添加处理器到日志记录器
logger.addHandler(file_handler)
logger.addHandler(console_handler)
# 记录初始化信息
logger.info('日志系统初始化成功')
logger.info(f'日志文件路径: {log_file}')
logger.debug('调试日志已启用')
return logger
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):
handler.setFormatter(
PrefixFormatter("%(asctime)s - %(levelname)s - %(message)s")
)
# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(PrefixFormatter("%(message)s"))
# 将控制台处理器添加到日志记录器
logging.getLogger().addHandler(console_handler)
# 打印日志目录所在路径
logging.info(f"Logger initialized, log directory: {os.path.abspath(log_dir)}")
# 导出日志记录器
logging = setup_logger()
def main_task():
"""

BIN
logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

5
qt.conf Normal file
View File

@@ -0,0 +1,5 @@
[Paths]
Plugins = PyQt6/Qt6/plugins
Binaries = PyQt6/Qt6/bin
Libraries = PyQt6/Qt6/lib
Translations = PyQt6/Qt6/translations

View File

@@ -1,4 +1,9 @@
DrissionPage==4.1.0.9
colorama==0.4.6
python-dotenv==1.0.0
pyinstaller
pyinstaller==6.3.0
PyQt6==6.6.1
PyQt6-Qt6==6.6.1
PyQt6-sip==13.6.0
requests==2.31.0
urllib3==2.1.0

4
version.json Normal file
View File

@@ -0,0 +1,4 @@
{
"version": "3.0.5",
"build": 5
}