Compare commits
17 Commits
458465770d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4daf592130 | ||
|
|
21f85ca6c1 | ||
|
|
8bff4ebdf7 | ||
|
|
d402ced90d | ||
|
|
98863b8cb5 | ||
|
|
0b6b3f13aa | ||
|
|
6a00193333 | ||
|
|
0e4087dd28 | ||
|
|
351b12a039 | ||
|
|
ca8cc199e9 | ||
|
|
0c9549bcdf | ||
|
|
39e0fbd305 | ||
|
|
7289d11749 | ||
|
|
793920e6ca | ||
|
|
840c4393d2 | ||
|
|
9f89dcba7b | ||
|
|
15541a9590 |
@@ -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.
|
||||
|
||||
35
README.md
35
README.md
@@ -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
171
build.py
@@ -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
40
create_icns.py
Executable 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
471
cursor_account_manager.py
Normal 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设备的唯一ID(32位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
880
cursor_gui.py
Normal 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
820
cursor_mac_id_modifier.sh
Normal 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
|
||||
|
||||
@@ -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
39
go_cursor_help.py
Normal 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
5
icons/app.svg
Normal 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
BIN
icons/logo.icns
Normal file
Binary file not shown.
14
icons/logo.svg
Normal file
14
icons/logo.svg
Normal 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 |
85
logger.py
85
logger.py
@@ -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():
|
||||
"""
|
||||
|
||||
5
qt.conf
Normal file
5
qt.conf
Normal file
@@ -0,0 +1,5 @@
|
||||
[Paths]
|
||||
Plugins = PyQt6/Qt6/plugins
|
||||
Binaries = PyQt6/Qt6/bin
|
||||
Libraries = PyQt6/Qt6/lib
|
||||
Translations = PyQt6/Qt6/translations
|
||||
@@ -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
4
version.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"version": "3.0.5",
|
||||
"build": 5
|
||||
}
|
||||
Reference in New Issue
Block a user