3 Commits

16 changed files with 1423 additions and 695 deletions

View File

@@ -13,7 +13,12 @@ from pathlib import Path
from utils.config import Config
from utils.cursor_registry import CursorRegistry
from cursor_auth_manager import CursorAuthManager
from utils.cursor_resetter import CursorResetter # 添加导入
from utils.cursor_resetter import CursorResetter
from datetime import datetime
# 添加缓存文件路径常量
CACHE_DIR = Path(os.path.expanduser("~")) / ".cursor_cache"
HARDWARE_ID_CACHE = CACHE_DIR / "hardware_id.json"
def is_admin() -> bool:
"""检查是否具有管理员权限
@@ -56,7 +61,11 @@ def run_as_admin():
return False
def get_hardware_id() -> str:
"""获取硬件唯一标识"""
"""获取硬件唯一标识
方案1: CPU ID + 主板序列号 + BIOS序列号
方案2: 系统盘序列号 + Windows安装时间
方案3: 计算机名(最后的备选方案)
"""
try:
# 创建startupinfo对象来隐藏命令行窗口
startupinfo = None
@@ -64,26 +73,70 @@ def get_hardware_id() -> str:
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 获取CPU信息
cpu_info = subprocess.check_output('wmic cpu get ProcessorId', startupinfo=startupinfo).decode()
cpu_id = cpu_info.split('\n')[1].strip()
# 获取主板序列号
board_info = subprocess.check_output('wmic baseboard get SerialNumber', startupinfo=startupinfo).decode()
board_id = board_info.split('\n')[1].strip()
# 获取BIOS序列号
bios_info = subprocess.check_output('wmic bios get SerialNumber', startupinfo=startupinfo).decode()
bios_id = bios_info.split('\n')[1].strip()
# 组合信息并生成哈希
combined = f"{cpu_id}:{board_id}:{bios_id}"
return hashlib.md5(combined.encode()).hexdigest()
# 方案1: 尝试获取硬件信息
try:
# 获取CPU ID
cpu_info = subprocess.check_output('wmic cpu get ProcessorId', startupinfo=startupinfo).decode()
cpu_id = cpu_info.split('\n')[1].strip()
# 获取主板序列号
board_info = subprocess.check_output('wmic baseboard get SerialNumber', startupinfo=startupinfo).decode()
board_id = board_info.split('\n')[1].strip()
# 获取BIOS序列号
bios_info = subprocess.check_output('wmic bios get SerialNumber', startupinfo=startupinfo).decode()
bios_id = bios_info.split('\n')[1].strip()
# 如果所有信息都获取成功且有效
if all([cpu_id, board_id, bios_id]) and not all(x in ['', '0', 'None', 'To be filled by O.E.M.'] for x in [cpu_id, board_id, bios_id]):
combined = f"{cpu_id}:{board_id}:{bios_id}"
hardware_id = hashlib.md5(combined.encode()).hexdigest()
logging.info("使用硬件信息生成ID成功")
return hardware_id
except Exception as e:
logging.warning(f"方案1失败: {str(e)}")
# 方案2: 系统盘序列号 + Windows安装时间
try:
backup_info = []
# 获取系统盘序列号
volume_info = subprocess.check_output('wmic logicaldisk where "DeviceID=\'C:\'" get VolumeSerialNumber', startupinfo=startupinfo).decode()
volume_serial = volume_info.split('\n')[1].strip()
if volume_serial and volume_serial not in ['', '0']:
backup_info.append(("volume", volume_serial))
# 获取Windows安装时间
os_info = subprocess.check_output('wmic os get InstallDate', startupinfo=startupinfo).decode()
install_date = os_info.split('\n')[1].strip()
if install_date:
backup_info.append(("install", install_date))
if backup_info:
combined = "|".join(f"{k}:{v}" for k, v in sorted(backup_info))
hardware_id = hashlib.md5(combined.encode()).hexdigest()
logging.info("使用系统信息生成ID成功")
return hardware_id
except Exception as e:
logging.warning(f"方案2失败: {str(e)}")
# 方案3: 使用计算机名(最后的备选方案)
import platform
computer_name = platform.node()
if computer_name:
hardware_id = hashlib.md5(computer_name.encode()).hexdigest()
logging.info("使用计算机名生成ID成功")
return hardware_id
raise ValueError("无法获取任何可用信息来生成硬件ID")
except Exception as e:
logging.error(f"获取硬件ID失败: {str(e)}")
# 如果获取失败使用UUID作为备选方案
return str(uuid.uuid4())
error_msg = f"生成硬件ID失败: {str(e)}"
logging.error(error_msg)
raise RuntimeError(error_msg)
class AccountSwitcher:
def __init__(self):
@@ -101,9 +154,9 @@ class AccountSwitcher:
self.package_json = self.app_path / "package.json"
self.auth_manager = CursorAuthManager()
self.config = Config()
self.hardware_id = self.get_hardware_id() # 先获取硬件ID
self.registry = CursorRegistry() # 添加注册表操作工具类
self.resetter = CursorResetter() # 添加重置工具类
self.hardware_id = get_hardware_id() # 使用新的硬件ID获取函数
self.registry = CursorRegistry()
self.resetter = CursorResetter()
self.max_retries = 5
self.wait_time = 1
@@ -111,33 +164,7 @@ class AccountSwitcher:
def get_hardware_id(self) -> str:
"""获取硬件唯一标识"""
try:
# 创建startupinfo对象来隐藏命令行窗口
startupinfo = None
if sys.platform == "win32":
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 获取CPU信息
cpu_info = subprocess.check_output('wmic cpu get ProcessorId', startupinfo=startupinfo).decode()
cpu_id = cpu_info.split('\n')[1].strip()
# 获取主板序列号
board_info = subprocess.check_output('wmic baseboard get SerialNumber', startupinfo=startupinfo).decode()
board_id = board_info.split('\n')[1].strip()
# 获取BIOS序列号
bios_info = subprocess.check_output('wmic bios get SerialNumber', startupinfo=startupinfo).decode()
bios_id = bios_info.split('\n')[1].strip()
# 组合信息并生成哈希
combined = f"{cpu_id}:{board_id}:{bios_id}"
return hashlib.md5(combined.encode()).hexdigest()
except Exception as e:
logging.error(f"获取硬件ID失败: {str(e)}")
# 如果获取失败使用UUID作为备选方案
return str(uuid.uuid4())
return get_hardware_id() # 使用全局函数
def get_cursor_version(self) -> str:
"""获取Cursor版本号"""
@@ -325,90 +352,30 @@ class AccountSwitcher:
return "网络请求失败,请稍后重试"
return str(error)
def get_process_details(self, process_name: str) -> List[Dict]:
"""获取进程详细信息
Args:
process_name: 进程名称
Returns:
List[Dict]: 进程详细信息列表
"""
try:
# 使用 tasklist 命令替代 wmi
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
output = subprocess.check_output(
f'tasklist /FI "IMAGENAME eq {process_name}" /FO CSV /NH',
startupinfo=startupinfo,
shell=True
).decode('gbk')
processes = []
if output.strip():
for line in output.strip().split('\n'):
if line.strip():
parts = line.strip('"').split('","')
if len(parts) >= 2:
processes.append({
'name': parts[0],
'pid': parts[1]
})
return processes
except Exception as e:
logging.error(f"获取进程信息失败: {str(e)}")
return []
def close_cursor_process(self) -> bool:
"""关闭所有Cursor进程
Returns:
bool: 是否成功关闭所有进程
"""
"""关闭所有Cursor相关进程"""
try:
if sys.platform == "win32":
# 创建startupinfo对象来隐藏命令行窗口
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 获取进程详情
processes = self.get_process_details("Cursor.exe")
if processes:
logging.info(f"发现 {len(processes)} 个Cursor进程")
for p in processes:
logging.info(f"进程信息: PID={p['pid']}, 路径={p['name']}")
# 直接使用taskkill命令强制结束所有相关进程
process_names = ["Cursor.exe", "devsense.php.ls.exe", "intelliphp.ls.exe"]
for name in process_names:
try:
subprocess.run(
f"taskkill /f /t /im {name} >nul 2>&1",
startupinfo=startupinfo,
shell=True,
timeout=5
)
except:
pass
# 尝试关闭进程
subprocess.run(
"taskkill /f /im Cursor.exe >nul 2>&1",
startupinfo=startupinfo,
shell=True
)
# 等待进程关闭
retry_count = 0
while retry_count < self.max_retries:
if not self.get_process_details("Cursor.exe"):
logging.info("所有Cursor进程已关闭")
return True
retry_count += 1
if retry_count >= self.max_retries:
processes = self.get_process_details("Cursor.exe")
if processes:
logging.error(f"无法关闭以下进程:")
for p in processes:
logging.error(f"PID={p['pid']}, 路径={p['name']}")
return False
logging.warning(f"等待进程关闭, 尝试 {retry_count}/{self.max_retries}...")
time.sleep(self.wait_time)
# 等待一小段时间确保进程完全关闭
time.sleep(2)
return True
else:
# 其他系统的处理
if sys.platform == "darwin":
subprocess.run("killall Cursor 2>/dev/null", shell=True)
else:
@@ -429,12 +396,11 @@ class AccountSwitcher:
return False
# 2. 使用新的重置工具类执行重置
success, message = self.resetter.reset_cursor(disable_update=True)
success, message = self.resetter.reset_cursor(disable_update=False) # 不在这里禁用更新
if not success:
logging.error(f"重置失败: {message}")
return False
# 不在这里重启Cursor让调用者决定何时重启
logging.info("机器码重置完成")
return True
@@ -443,102 +409,57 @@ class AccountSwitcher:
return False
def restart_cursor(self) -> bool:
"""重启Cursor编辑器
Returns:
bool: 是否成功重启
"""
"""重启Cursor编辑器"""
try:
logging.info("正在重启Cursor...")
# 1. 确保进程已关闭
self.close_cursor_process()
# 确保进程已关闭
if not self.close_cursor_process():
logging.error("无法关闭Cursor进程")
return False
# 等待进程完全关闭
# 2. 等待一小段时间
time.sleep(2)
# 启动Cursor
# 3. 启动Cursor
if sys.platform == "win32":
cursor_exe = self.cursor_path / "Cursor.exe"
if cursor_exe.exists():
try:
# 使用subprocess启动
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
subprocess.Popen(
str(cursor_exe),
startupinfo=startupinfo,
creationflags=subprocess.CREATE_NEW_CONSOLE
# 使用ShellExecuteW以管理员权限启动
result = ctypes.windll.shell32.ShellExecuteW(
None, # 父窗口句柄
"runas", # 以管理员权限运行
str(cursor_exe), # 程序路径
None, # 参数
str(cursor_exe.parent), # 工作目录
1 # 正常窗口显示
)
# 等待进程启动
time.sleep(3)
# 验证进程是否启动
processes = self.get_process_details("Cursor.exe")
if processes:
logging.info("Cursor启动成功")
# ShellExecuteW返回值大于32表示成功
if result > 32:
time.sleep(3) # 等待启动
return True
else:
logging.error("Cursor进程未找到")
# 尝试使用 os.startfile 作为备选方案
try:
os.startfile(str(cursor_exe))
time.sleep(3)
logging.info("使用备选方案启动Cursor")
return True
except Exception as e:
logging.error(f"备选启动方案失败: {str(e)}")
return False
logging.error(f"启动Cursor失败,错误码: {result}")
return False
except Exception as e:
logging.error(f"启动Cursor失败: {str(e)}")
# 尝试使用 os.startfile 作为备选方案
try:
os.startfile(str(cursor_exe))
time.sleep(3)
logging.info("使用备选方案启动Cursor")
return True
except Exception as e:
logging.error(f"备选启动方案失败: {str(e)}")
return False
return False
else:
logging.error(f"未找到Cursor程序: {cursor_exe}")
return False
elif sys.platform == "darwin":
else:
try:
subprocess.run("open -a Cursor", shell=True, check=True)
logging.info("Cursor启动成功")
if sys.platform == "darwin":
subprocess.run("open -a Cursor", shell=True, check=True)
else:
subprocess.run("sudo cursor &", shell=True, check=True)
time.sleep(3)
return True
except subprocess.CalledProcessError as e:
logging.error(f"启动Cursor失败: {str(e)}")
return False
elif sys.platform == "linux":
try:
subprocess.run("cursor &", shell=True, check=True)
logging.info("Cursor启动成功")
return True
except subprocess.CalledProcessError as e:
except Exception as e:
logging.error(f"启动Cursor失败: {str(e)}")
return False
return False
except Exception as e:
logging.error(f"重启Cursor失败: {str(e)}")
# 尝试使用 os.startfile 作为最后的备选方案
try:
cursor_exe = self.cursor_path / "Cursor.exe"
if cursor_exe.exists():
os.startfile(str(cursor_exe))
time.sleep(3)
logging.info("使用最终备选方案启动Cursor")
return True
except:
pass
return False
def activate_and_switch(self, activation_code: str) -> Tuple[bool, str]:
@@ -729,12 +650,14 @@ class AccountSwitcher:
if not self.reset_machine_id():
return False, "重置机器码失败"
# 7. 重启Cursor(只在这里执行一次重启)
# 7. 重启Cursor
logging.info("正在重启Cursor...")
retry_count = 0
max_retries = 3
while retry_count < max_retries:
if self.restart_cursor():
# 等待进程完全启动
time.sleep(5)
break
retry_count += 1
if retry_count < max_retries:
@@ -777,50 +700,17 @@ class AccountSwitcher:
"""禁用Cursor更新"""
try:
# 1. 先关闭所有Cursor进程
if sys.platform == "win32":
# 创建startupinfo对象来隐藏命令行窗口
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 关闭Cursor
subprocess.run(
"taskkill /f /im Cursor.exe >nul 2>&1",
startupinfo=startupinfo,
shell=True
)
time.sleep(2)
if not self.close_cursor_process():
return False, "无法关闭Cursor进程请手动关闭后重试"
# 2. 删除updater目录并创建同名文件以阻止更新
updater_path = Path(os.getenv('LOCALAPPDATA')) / "cursor-updater"
# 2. 调用resetter的disable_auto_update方法
success, message = self.resetter.disable_auto_update()
if not success:
logging.error(f"禁用自动更新失败: {message}")
return False, f"禁用更新失败: {message}"
try:
# 如果是目录,则删除
if updater_path.is_dir():
import shutil
shutil.rmtree(str(updater_path))
logging.info("删除updater目录成功")
# 如果是文件,则删除
if updater_path.is_file():
os.remove(str(updater_path))
logging.info("删除updater文件成功")
# 创建同名空文件
updater_path.touch()
logging.info("创建updater空文件成功")
# 3. 重启Cursor
cursor_exe = self.cursor_path / "Cursor.exe"
if cursor_exe.exists():
os.startfile(str(cursor_exe))
logging.info("Cursor重启成功")
return True, "Cursor更新已禁用程序已重启"
except Exception as e:
logging.error(f"操作updater文件失败: {str(e)}")
return False, f"禁用更新失败: {str(e)}"
logging.info("更新已禁用")
return True, "Cursor更新已禁用"
except Exception as e:
logging.error(f"禁用Cursor更新失败: {str(e)}")
@@ -920,6 +810,26 @@ class AccountSwitcher:
return False, "多次尝试后心跳发送失败"
def bypass_cursor_limit(self) -> Tuple[bool, str]:
"""突破Cursor使用限制"""
try:
# 1. 先关闭所有Cursor进程
if not self.close_cursor_process():
return False, "无法关闭Cursor进程请手动关闭后重试"
# 2. 调用resetter的reset_cursor方法
success, message = self.resetter.reset_cursor(disable_update=True)
if not success:
logging.error(f"突破限制失败: {message}")
return False, f"突破限制失败: {message}"
logging.info("突破限制成功")
return True, "突破限制成功"
except Exception as e:
logging.error(f"突破限制失败: {str(e)}")
return False, f"突破限制失败: {str(e)}"
def main():
"""主函数"""
try:
@@ -935,13 +845,15 @@ def main():
switcher = AccountSwitcher()
print("\n=== Cursor账号切换工具 ===")
print("1. 激活并切换账号")
print("2. 仅重置机器码")
print("1. 禁用自动更新")
print("2. 突破使用限制")
print("3. 激活并切换账号")
print("4. 仅重置机器码")
while True:
try:
choice = int(input("\n请选择操作 (1 或 2): ").strip())
if choice in [1, 2]:
choice = int(input("\n请选择操作 (1-4): ").strip())
if choice in [1, 2, 3, 4]:
break
else:
print("无效的选项,请重新输入")
@@ -949,6 +861,18 @@ def main():
print("请输入有效的数字")
if choice == 1:
success, message = switcher.disable_cursor_update()
if success:
print("\n更新已禁用!")
else:
print(f"\n禁用更新失败: {message}")
elif choice == 2:
success, message = switcher.bypass_cursor_limit()
if success:
print("\n突破限制成功!")
else:
print(f"\n突破限制失败: {message}")
elif choice == 3:
activation_code = input("请输入激活码: ").strip()
if switcher.activate_and_switch(activation_code):
print("\n账号激活成功!")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 38 KiB

138
build.bat
View File

@@ -1,65 +1,119 @@
@echo off
chcp 65001
echo 开始打包流程...
setlocal EnableDelayedExpansion
echo 开始正式版本打包...
:: 设置工作目录为脚本所在目录
cd /d "%~dp0"
:: 激活虚拟环境
if exist "venv\Scripts\activate.bat" (
echo 激活虚拟环境...
call venv\Scripts\activate.bat
) else (
echo 警告: 未找到虚拟环境,使用系统 Python
)
:: 确保安装了必要的包
echo 检查依赖包...
pip install -r requirements.txt
:: 更新版本号
python update_version.py
:: 读取版本号
set /p VERSION=<version.txt
echo 当前版本: %VERSION%
echo 当前版本: !VERSION!
:: 提取主版本号和次版本号 (3.4.4 -> 3.4)
for /f "tokens=1,2 delims=." %%a in ("%VERSION%") do (
:: 提取主版本号和次版本号 (3.4.7 -> 3.4)
for /f "tokens=1,2 delims=." %%a in ("!VERSION!") do (
set MAJOR_VERSION=%%a.%%b
)
echo 主版本目录: %MAJOR_VERSION%
echo 主版本目录: !MAJOR_VERSION!
:: 创建版本目录
set VERSION_DIR=dist\%MAJOR_VERSION%
if not exist "%VERSION_DIR%" (
mkdir "%VERSION_DIR%"
echo 创建目录: %VERSION_DIR%
set VERSION_DIR=dist\!MAJOR_VERSION!
if not exist "!VERSION_DIR!" (
mkdir "!VERSION_DIR!"
echo 创建目录: !VERSION_DIR!
)
:: 使用新的spec文件进行打包
pyinstaller --noconfirm build_nezha.spec
:: 清理 Python 缓存文件
echo 清理Python缓存文件...
for /d /r . %%d in (__pycache__) do @if exist "%%d" rd /s /q "%%d"
del /s /q *.pyc >nul 2>&1
del /s /q *.pyo >nul 2>&1
:: 检查源文件是否存在
echo 检查文件: dist\听泉cursor助手%VERSION%.exe
if not exist "dist\听泉cursor助手%VERSION%.exe" (
echo 错误: 打包后的文件不存在
echo 预期文件路径: dist\听泉cursor助手%VERSION%.exe
:: 清理旧的打包文件
echo 清理旧文件...
if exist "build" rd /s /q "build"
if exist "*.spec" del /f /q "*.spec"
:: 使用优化选项进行打包
echo 开始打包...
pyinstaller ^
--noconfirm ^
--clean ^
--onefile ^
--noconsole ^
--icon=icon/two.ico ^
--name "听泉cursor助手!VERSION!" ^
--add-data "icon;icon" ^
--add-data "version.txt;." ^
--add-data "testversion.txt;." ^
--add-data "requirements.txt;." ^
--exclude-module _tkinter ^
--exclude-module tkinter ^
--exclude-module PIL.ImageTk ^
--exclude-module PIL.ImageWin ^
--exclude-module numpy ^
--exclude-module pandas ^
--exclude-module matplotlib ^
--exclude "__pycache__" ^
--exclude "*.pyc" ^
--exclude "*.pyo" ^
--exclude "*.pyd" ^
main.py
:: 检查打包结果并移动文件
set TEMP_FILE=dist\听泉cursor助手!VERSION!.exe
set TARGET_FILE=!VERSION_DIR!\听泉cursor助手v!VERSION!.exe
echo 检查文件: !TEMP_FILE!
if exist "!TEMP_FILE!" (
echo 正式版本打包成功!
:: 移动到版本目录
echo 移动文件到: !TARGET_FILE!
move "!TEMP_FILE!" "!TARGET_FILE!"
:: 显示文件大小
for %%I in ("!TARGET_FILE!") do (
echo 文件大小: %%~zI 字节
)
echo.
echo 正式版本构建完成!
echo 版本号: v!VERSION!
echo 文件位置: !TARGET_FILE!
) else (
echo 错误: 打包失败,文件不存在
echo 预期文件路径: !TEMP_FILE!
dir /b dist
pause
exit /b 1
)
:: 检查目标目录是否存在
echo 检查目标目录: %VERSION_DIR%
if not exist "%VERSION_DIR%" (
echo 错误: 目标目录不存在
pause
exit /b 1
:: 清理临时文件
echo 清理临时文件...
if exist "build" rd /s /q "build"
if exist "dist\听泉cursor助手!VERSION!.exe" del /f /q "dist\听泉cursor助手!VERSION!.exe"
if exist "*.spec" del /f /q "*.spec"
:: 退出虚拟环境
if exist "venv\Scripts\activate.bat" (
echo 退出虚拟环境...
deactivate
)
:: 移动文件到版本目录
echo 移动文件:
echo 源文件: dist\听泉cursor助手%VERSION%.exe
echo 目标文件: %VERSION_DIR%\听泉cursor助手v%VERSION%.exe
move "dist\听泉cursor助手%VERSION%.exe" "%VERSION_DIR%\听泉cursor助手v%VERSION%.exe"
if errorlevel 1 (
echo 移动文件失败,请检查:
echo 1. 源文件是否存在: dist\听泉cursor助手%VERSION%.exe
echo 2. 目标目录是否可写: %VERSION_DIR%
echo 3. 目标文件是否已存在: %VERSION_DIR%\听泉cursor助手v%VERSION%.exe
dir /b dist
dir /b "%VERSION_DIR%"
pause
exit /b 1
)
echo.
echo 打包完成!
echo 文件保存在: %VERSION_DIR%\听泉cursor助手v%VERSION%.exe
endlocal
pause

View File

@@ -1,70 +0,0 @@
# -*- mode: python ; coding: utf-8 -*-
import os
import sys
from PyInstaller.utils.hooks import collect_all
def get_version():
with open('version.txt', 'r', encoding='utf-8') as f:
version = f.read().strip()
return version
version = get_version()
# 收集所有需要的依赖
datas = [('icon', 'icon'), ('version.txt', '.')]
binaries = []
hiddenimports = [
'win32gui', 'win32con', 'win32process', 'psutil', # Windows API 相关
'PyQt5', 'PyQt5.QtCore', 'PyQt5.QtGui', 'PyQt5.QtWidgets', # GUI 相关
'PyQt5.sip', # PyQt5 必需
'PyQt5.QtNetwork', # 网络相关
'PIL', # Pillow 相关
'PIL._imaging', # Pillow 核心
'requests', 'urllib3', 'certifi', # 网络请求相关
'json', 'uuid', 'hashlib', 'logging', # 基础功能相关
'importlib', # 导入相关
'pkg_resources', # 包资源
]
# 主要的分析对象
a = Analysis(
['main.py'],
pathex=[],
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=['_tkinter', 'tkinter', 'Tkinter'],
win_no_prefer_redirects=False,
win_private_assemblies=False,
noarchive=False,
module_collection_mode={'PyQt5': 'pyz+py'},
)
# 创建PYZ
pyz = PYZ(a.pure)
# 创建EXE
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name=f'听泉cursor助手{version}',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['icon\\two.ico'],
)

View File

@@ -348,27 +348,103 @@ class CursorAuthManager:
# 确保进程已关闭
if not self.close_cursor_process():
logging.error("无法关闭Cursor进程")
return False
# 等待系统资源释放
time.sleep(3)
# 启动Cursor
if sys.platform == "win32":
cursor_exe = self.cursor_path / "Cursor.exe"
if cursor_exe.exists():
os.startfile(str(cursor_exe))
logging.info("Cursor启动成功")
return True
max_retries = 3
for attempt in range(max_retries):
try:
# 检查是否还有Cursor进程在运行
try:
subprocess.check_output(
"tasklist | findstr Cursor.exe",
startupinfo=subprocess.STARTUPINFO(),
shell=True
)
# 如果找到进程,等待它关闭
if attempt < max_retries - 1:
logging.info("等待进程完全关闭...")
time.sleep(3)
continue
else:
logging.error("无法确保所有Cursor进程已关闭")
return False
except subprocess.CalledProcessError:
# 没有找到进程,可以继续启动
pass
# 使用subprocess启动
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
# 使用subprocess.Popen启动并等待一段时间
process = subprocess.Popen(
str(cursor_exe),
startupinfo=startupinfo,
creationflags=subprocess.CREATE_NEW_CONSOLE
)
# 等待进程启动
time.sleep(5)
# 验证进程是否成功启动
if process.poll() is None: # 进程仍在运行
logging.info("Cursor启动成功")
return True
else:
if attempt < max_retries - 1:
logging.warning(f"启动尝试 {attempt + 1} 失败,准备重试...")
time.sleep(3)
continue
logging.error("Cursor进程启动失败")
# 尝试使用 os.startfile 作为备选方案
try:
os.startfile(str(cursor_exe))
time.sleep(5)
logging.info("使用备选方案启动Cursor")
return True
except Exception as e:
logging.error(f"备选启动方案失败: {str(e)}")
return False
except Exception as e:
if attempt < max_retries - 1:
logging.warning(f"启动尝试 {attempt + 1} 失败: {str(e)},准备重试...")
time.sleep(3)
continue
logging.error(f"启动Cursor失败: {str(e)}")
return False
else:
logging.error(f"未找到Cursor程序: {cursor_exe}")
return False
elif sys.platform == "darwin":
subprocess.run("open -a Cursor", shell=True)
logging.info("Cursor启动成功")
return True
try:
subprocess.run("open -a Cursor", shell=True, check=True)
time.sleep(5)
logging.info("Cursor启动成功")
return True
except subprocess.CalledProcessError as e:
logging.error(f"启动Cursor失败: {str(e)}")
return False
elif sys.platform == "linux":
subprocess.run("cursor &", shell=True)
logging.info("Cursor启动成功")
return True
try:
subprocess.run("cursor &", shell=True, check=True)
time.sleep(5)
logging.info("Cursor启动成功")
return True
except subprocess.CalledProcessError as e:
logging.error(f"启动Cursor失败: {str(e)}")
return False
return False
except Exception as e:

View File

@@ -40,10 +40,9 @@ Write-Host @"
"@
Write-Host "$BLUE================================$NC"
Write-Host "$GREEN Cursor 设备ID 修改工具 $NC"
Write-Host "$YELLOW 关注公众号【煎饼果子卷AI】 $NC"
Write-Host "$YELLOW 一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) $NC"
Write-Host "$YELLOW [重要提示] 本工具免费如果对您有帮助请关注公众号【煎饼果子卷AI】 $NC"
Write-Host "$GREEN 听泉Cursor助手 - 机器码重置工具 $NC"
Write-Host "$YELLOW 官方网站: cursor.nosqli.com $NC"
Write-Host "$YELLOW 微信客服: behikcigar $NC"
Write-Host "$BLUE================================$NC"
Write-Host ""
@@ -191,26 +190,81 @@ $randomPart = Get-RandomHex -length 32
$MACHINE_ID = "$prefixHex$randomPart"
$SQM_ID = "{$([System.Guid]::NewGuid().ToString().ToUpper())}"
# 在生成新ID后直接执行注册表操作移除询问
# 在Update-MachineGuid函数前添加权限检查
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) {
Write-Host "$RED[错误]$NC 请使用管理员权限运行此脚本"
Start-Process powershell "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs
exit
}
function Update-MachineGuid {
try {
$newMachineGuid = [System.Guid]::NewGuid().ToString()
# 先检查注册表路径是否存在
$registryPath = "HKLM:\SOFTWARE\Microsoft\Cryptography"
if (-not (Test-Path $registryPath)) {
throw "注册表路径不存在: $registryPath"
}
# 获取当前的 MachineGuid
$currentGuid = Get-ItemProperty -Path $registryPath -Name MachineGuid -ErrorAction Stop
if (-not $currentGuid) {
throw "无法获取当前的 MachineGuid"
}
$originalGuid = $currentGuid.MachineGuid
Write-Host "$GREEN[信息]$NC 当前注册表值:"
Write-Host "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography"
Write-Host " MachineGuid REG_SZ $originalGuid"
# 创建备份目录(如果不存在)
if (-not (Test-Path $BACKUP_DIR)) {
New-Item -ItemType Directory -Path $BACKUP_DIR -Force | Out-Null
}
# 创建备份文件
$backupFile = "$BACKUP_DIR\MachineGuid_$(Get-Date -Format 'yyyyMMdd_HHmmss').reg"
$backupResult = Start-Process "reg.exe" -ArgumentList "export", "`"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography`"", "`"$backupFile`"" -NoNewWindow -Wait -PassThru
# 备份原始值
$originalGuid = (Get-ItemProperty -Path $registryPath -Name "MachineGuid").MachineGuid
$backupFile = "$BACKUP_DIR\MachineGuid.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')"
$originalGuid | Out-File $backupFile -Encoding UTF8
if ($backupResult.ExitCode -eq 0) {
Write-Host "$GREEN[信息]$NC 注册表项已备份到:$backupFile"
} else {
Write-Host "$YELLOW[警告]$NC 备份创建失败,继续执行..."
}
# 生成新GUID
$newGuid = [System.Guid]::NewGuid().ToString()
# 更新注册表
Set-ItemProperty -Path $registryPath -Name "MachineGuid" -Value $newMachineGuid
Write-Host "$GREEN[信息]$NC 已更新系统 MachineGuid: $newMachineGuid"
Write-Host "$GREEN[信息]$NC 原始值已备份至: $backupFile"
Write-Host "$GREEN[信息]$NC 注册表路径: 计算机\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography"
Write-Host "$GREEN[信息]$NC 注册表项名: MachineGuid"
Set-ItemProperty -Path $registryPath -Name MachineGuid -Value $newGuid -Force -ErrorAction Stop
# 验证更新
$verifyGuid = (Get-ItemProperty -Path $registryPath -Name MachineGuid -ErrorAction Stop).MachineGuid
if ($verifyGuid -ne $newGuid) {
throw "注册表验证失败:更新后的值 ($verifyGuid) 与预期值 ($newGuid) 不匹配"
}
Write-Host "$GREEN[信息]$NC 注册表更新成功:"
Write-Host "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography"
Write-Host " MachineGuid REG_SZ $newGuid"
return $true
}
catch {
Write-Host "$RED[错误]$NC 更新系统 MachineGuid 失败: $_"
Write-Host "$RED[错误]$NC 注册表操作失败:$($_.Exception.Message)"
# 尝试恢复备份
if ($backupFile -and (Test-Path $backupFile)) {
Write-Host "$YELLOW[恢复]$NC 正在从备份恢复..."
$restoreResult = Start-Process "reg.exe" -ArgumentList "import", "`"$backupFile`"" -NoNewWindow -Wait -PassThru
if ($restoreResult.ExitCode -eq 0) {
Write-Host "$GREEN[恢复成功]$NC 已还原原始注册表值"
} else {
Write-Host "$RED[错误]$NC 恢复失败,请手动导入备份文件:$backupFile"
}
} else {
Write-Host "$YELLOW[警告]$NC 未找到备份文件或备份创建失败,无法自动恢复"
}
return $false
}
}
@@ -297,7 +351,7 @@ try {
# 显示公众号信息
Write-Host ""
Write-Host "$GREEN================================$NC"
Write-Host "$YELLOW 关注公众号【煎饼果子卷AI】一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) $NC"
Write-Host "$YELLOW 欢迎使用Cursor助手 $NC"
Write-Host "$GREEN================================$NC"
Write-Host ""
Write-Host "$GREEN[信息]$NC 请重启 Cursor 以应用新的配置"
@@ -413,6 +467,9 @@ try {
Write-Host "$GREEN[信息]$NC 保持默认设置,不进行更改"
}
# 保留有效的注册表更新
Update-MachineGuid
} catch {
Write-Host "$RED[错误]$NC 主要操作失败: $_"
Write-Host "$YELLOW[尝试]$NC 使用备选方法..."

83
disable_update.ps1 Normal file
View File

@@ -0,0 +1,83 @@
# 检查管理员权限
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
Write-Host "`n[错误] 请以管理员身份运行此脚本" -ForegroundColor Red
Write-Host "请使用 'Start-Process pwsh -Verb RunAs' 启动管理员PowerShell" -ForegroundColor Yellow
Pause
exit
}
Write-Host "`n=== Cursor更新禁用工具 ===" -ForegroundColor Cyan
# 定义路径
$updaterPath = Join-Path $env:LOCALAPPDATA "cursor-updater"
$cursorPath = Join-Path $env:LOCALAPPDATA "Programs\cursor"
$packageJsonPath = Join-Path $cursorPath "resources\app\package.json"
Write-Host "`n正在检查路径..." -ForegroundColor Gray
Write-Host "updater路径: $updaterPath" -ForegroundColor Gray
try {
# 1. 删除现有的updater文件/目录
if (Test-Path $updaterPath) {
Write-Host "`n发现现有updater文件/目录,正在删除..." -ForegroundColor Yellow
Remove-Item -Path $updaterPath -Force -Recurse -ErrorAction Stop
Write-Host "成功删除现有文件/目录" -ForegroundColor Green
}
# 2. 创建空文件
Write-Host "`n正在创建updater文件..." -ForegroundColor Yellow
New-Item -Path $updaterPath -ItemType File -Force -ErrorAction Stop | Out-Null
Write-Host "成功创建updater文件" -ForegroundColor Green
# 3. 设置文件权限
Write-Host "`n正在设置文件权限..." -ForegroundColor Yellow
# 获取当前用户
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
# 创建新的ACL
$acl = New-Object System.Security.AccessControl.FileSecurity
$acl.SetAccessRuleProtection($true, $false) # 禁用继承
# 添加只读权限规则
$readRule = New-Object System.Security.AccessControl.FileSystemAccessRule(
$currentUser,
[System.Security.AccessControl.FileSystemRights]::Read,
[System.Security.AccessControl.AccessControlType]::Allow
)
$acl.AddAccessRule($readRule)
# 应用ACL
Set-Acl -Path $updaterPath -AclObject $acl -ErrorAction Stop
# 设置只读属性
Set-ItemProperty -Path $updaterPath -Name IsReadOnly -Value $true -ErrorAction Stop
Write-Host "成功设置文件权限" -ForegroundColor Green
# 4. 修改package.json
if (Test-Path $packageJsonPath) {
Write-Host "`n正在修改package.json..." -ForegroundColor Yellow
$json = Get-Content $packageJsonPath -Raw | ConvertFrom-Json
$json.updateUrl = ""
$json.disableUpdate = $true
$json | ConvertTo-Json -Depth 10 | Set-Content $packageJsonPath -Encoding UTF8
Write-Host "成功修改package.json" -ForegroundColor Green
}
# 5. 验证权限
Write-Host "`n正在验证文件权限..." -ForegroundColor Yellow
$item = Get-Item $updaterPath
if (-not $item.IsReadOnly) {
throw "文件权限验证失败:文件不是只读"
}
Write-Host "文件权限验证通过" -ForegroundColor Green
Write-Host "`n[成功] Cursor更新已禁用" -ForegroundColor Green
} catch {
Write-Host "`n[错误] 操作失败: $($_.Exception.Message)" -ForegroundColor Red
}
Write-Host "`n按任意键退出..." -ForegroundColor Gray
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

View File

@@ -6,9 +6,9 @@ from PIL import Image
from PyQt5.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QLineEdit, QPushButton, QFrame, QTextEdit,
QMessageBox, QApplication, QSystemTrayIcon, QMenu,
QDialog, QProgressBar, QStyle)
QDialog, QProgressBar, QStyle, QDialogButtonBox, QToolTip)
from PyQt5.QtCore import Qt, QTimer, QThread, pyqtSignal
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtGui import QIcon, QPixmap, QCursor
import time
import requests
from urllib.parse import quote
@@ -608,6 +608,30 @@ class MainWindow(QMainWindow):
disable_update_btn.setMinimumWidth(500)
btn_layout.addWidget(disable_update_btn, 0, Qt.AlignCenter)
# 重启Cursor按钮
self.restart_cursor_btn = QPushButton("重启Cursor")
self.restart_cursor_btn.setFixedHeight(30)
self.restart_cursor_btn.setStyleSheet("""
QPushButton {
background-color: #2d8cf0;
border: none;
color: white;
padding: 5px 15px;
border-radius: 4px;
font-size: 13px;
}
QPushButton:hover {
background-color: #2b85e4;
}
QPushButton:pressed {
background-color: #2979d9;
}
QPushButton:disabled {
background-color: #bbbec4;
}
""")
self.restart_cursor_btn.clicked.connect(self.restart_cursor)
main_layout.addWidget(btn_frame)
# 检查更新按钮
@@ -1336,91 +1360,11 @@ class MainWindow(QMainWindow):
try:
# 显示加载对话框
self.show_loading_dialog("正在禁用更新,请稍候...")
self.show_loading_dialog("正在禁用更新...")
# 创建工作线程
from utils.cursor_registry import CursorRegistry
registry = CursorRegistry()
def disable_func():
try:
# 1. 先关闭所有Cursor进程
if sys.platform == "win32":
# 创建startupinfo对象来隐藏命令行窗口
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
# 关闭Cursor
subprocess.run(
"taskkill /f /im Cursor.exe >nul 2>&1",
startupinfo=startupinfo,
shell=True
)
time.sleep(2)
# 2. 处理updater文件
updater_path = Path(os.getenv('LOCALAPPDATA')) / "cursor-updater"
try:
# 如果是目录,则删除
if updater_path.is_dir():
import shutil
shutil.rmtree(str(updater_path))
logging.info("删除updater目录成功")
# 如果是文件,则删除
if updater_path.is_file():
updater_path.unlink()
logging.info("删除updater文件成功")
# 创建阻止文件
updater_path.touch()
logging.info("创建updater空文件成功")
# 设置文件权限
import subprocess
import stat
# 设置只读属性
os.chmod(str(updater_path), stat.S_IREAD)
logging.info("设置只读属性成功")
# 使用icacls设置权限只读
username = os.getenv('USERNAME')
cmd = f'icacls "{str(updater_path)}" /inheritance:r /grant:r "{username}:(R)"'
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode != 0:
logging.error(f"设置文件权限失败: {result.stderr}")
return False, "设置文件权限失败"
logging.info("设置文件权限成功")
# 验证设置
if not os.path.exists(updater_path):
return False, "文件创建失败"
if os.access(str(updater_path), os.W_OK):
return False, "文件权限设置失败"
except Exception as e:
logging.error(f"处理updater文件失败: {str(e)}")
return False, "处理updater文件失败"
# 3. 修改package.json配置
if not registry.fix_cursor_startup():
return False, "修改配置失败"
# 4. 重启Cursor
cursor_exe = registry.cursor_path / "Cursor.exe"
if cursor_exe.exists():
os.startfile(str(cursor_exe))
logging.info("Cursor重启成功")
return True, "Cursor更新已禁用程序已重启"
else:
return False, "未找到Cursor程序"
except Exception as e:
logging.error(f"禁用更新时发生错误: {str(e)}")
return False, str(e)
self.worker = ApiWorker(disable_func)
self.worker.finished.connect(lambda result: self.on_disable_update_complete(result))
self.worker = ApiWorker(self.switcher.disable_cursor_update)
self.worker.finished.connect(self.on_disable_update_complete)
self.worker.start()
except Exception as e:
@@ -1429,167 +1373,243 @@ class MainWindow(QMainWindow):
self.show_custom_error("禁用更新失败", str(e))
def on_disable_update_complete(self, result):
"""禁用更新完成回调"""
success, data = result
self.hide_loading_dialog()
self._request_complete()
if success:
self.show_custom_message(
"成功",
"禁用更新成功",
data,
QStyle.SP_DialogApplyButton,
"#198754"
)
else:
self.show_custom_error("禁用更新失败", str(data))
"""禁用更新完成回调"""
try:
success, data = result
logging.info(f"禁用更新操作结果: success={success}, data={data}")
self.hide_loading_dialog()
self._request_complete()
if isinstance(data, tuple):
inner_success, inner_message = data
if inner_success:
self.show_custom_message(
"成功",
"禁用更新成功",
inner_message,
QMessageBox.Information,
"#198754"
)
QTimer.singleShot(1000, self.check_status)
else:
# 创建自定义对话框
dialog = QDialog(self)
dialog.setWindowTitle("禁用更新失败")
dialog.setFixedWidth(400)
layout = QVBoxLayout()
# 警告图标和标题
header_layout = QHBoxLayout()
warning_icon = QLabel()
warning_icon.setPixmap(self.style().standardIcon(QStyle.SP_MessageBoxWarning).pixmap(32, 32))
header_layout.addWidget(warning_icon)
header_label = QLabel("需要手动操作")
header_label.setStyleSheet("color: red; font-size: 16px; font-weight: bold;")
header_layout.addWidget(header_label)
header_layout.addStretch()
layout.addLayout(header_layout)
# 分隔线
line = QFrame()
line.setFrameShape(QFrame.HLine)
line.setFrameShadow(QFrame.Sunken)
layout.addWidget(line)
# 手动步骤说明
steps_label = QLabel(
"请按以下步骤手动操作:\n\n"
"1. 按下 Win + R 组合键\n"
"2. 在运行框中输入 powershell 或 pwsh\n"
"3. 按 Ctrl + Shift + Enter 以管理员身份运行\n"
"4. 在管理员终端中输入以下命令:"
)
steps_label.setWordWrap(True)
steps_label.setStyleSheet("margin: 10px 0;")
layout.addWidget(steps_label)
# 命令文本框和复制按钮
command_layout = QHBoxLayout()
command_text = QLineEdit()
command_text.setText("irm https://github.com/maticarmy/cursor-nosqli-tools/blob/main/scripts/run/cursor_win_id_modifier.ps1 | iex")
command_text.setReadOnly(True)
command_layout.addWidget(command_text)
copy_button = QPushButton("复制")
copy_button.setStyleSheet("""
QPushButton {
background-color: #007bff;
color: white;
border: none;
padding: 5px 15px;
border-radius: 3px;
}
QPushButton:hover {
background-color: #0056b3;
}
""")
copy_button.clicked.connect(lambda: self.copy_and_show_tip(command_text, command_text.text(), "命令已复制到剪贴板"))
command_layout.addWidget(copy_button)
layout.addLayout(command_layout)
# 按钮
button_box = QDialogButtonBox(QDialogButtonBox.Ok)
button_box.accepted.connect(dialog.accept)
layout.addWidget(button_box)
dialog.setLayout(layout)
dialog.exec_()
else:
self.show_custom_error("禁用更新失败", str(data))
except Exception as e:
logging.error(f"处理禁用更新回调时发生错误: {str(e)}")
self.show_custom_error("错误", "处理结果时发生错误,请重试")
def bypass_cursor_limit(self):
"""突破Cursor版本限制"""
if not self.check_activation_status():
return
if not self._check_request_throttle():
return
# 显示加载对话框
self.show_loading_dialog("正在突破限制...")
# 创建工作线程
self.worker = ApiWorker(self.switcher.bypass_cursor_limit)
self.worker.finished.connect(self.on_bypass_limit_complete)
self.worker.start()
def on_bypass_limit_complete(self, result):
"""突破限制完成回调"""
try:
# 显示加载对话框
self.show_loading_dialog("正在突破版本限制,请稍候...")
success, data = result
logging.info(f"突破限制操作结果: success={success}, data={data}")
# 创建工作线程
from utils.cursor_registry import CursorRegistry
registry = CursorRegistry()
# 确保在主线程中执行 UI 操作
self.hide_loading_dialog()
self._request_complete()
def reset_func():
try:
# 1. 先关闭所有Cursor进程
if sys.platform == "win32":
os.system("taskkill /f /im Cursor.exe >nul 2>&1")
time.sleep(2)
if isinstance(data, tuple):
inner_success, inner_message = data
if inner_success:
try:
logging.info("准备显示成功消息对话框")
self.show_custom_message(
"成功",
"突破限制成功",
inner_message,
QStyle.SP_DialogApplyButton,
"#198754"
)
logging.info("成功消息对话框显示完成")
# 更新状态显示
QTimer.singleShot(1000, self.check_status)
logging.info("已安排状态更新")
except Exception as e:
logging.error(f"显示成功消息时发生错误: {str(e)}")
# 使用更简单的消息框作为后备方案
QMessageBox.information(self, "成功", "突破限制成功")
else:
try:
logging.info("准备显示错误消息对话框")
self.show_custom_error("突破限制失败", str(inner_message))
logging.info("错误消息对话框显示完成")
except Exception as e:
logging.error(f"显示错误消息时发生错误: {str(e)}")
# 使用更简单的消息框作为后备方案
QMessageBox.critical(self, "错误", f"突破限制失败: {str(inner_message)}")
else:
self.show_custom_error("突破限制失败", str(data))
# 2. 清理注册表
if not registry.clean_registry():
return False, "清理注册表失败"
# 3. 清理文件
if not registry.clean_cursor_files():
return False, "清理文件失败"
# 4. 重启Cursor
cursor_exe = self.cursor_path / "Cursor.exe"
if cursor_exe.exists():
os.startfile(str(cursor_exe))
logging.info("Cursor重启成功")
return True, "突破限制成功"
else:
return False, "未找到Cursor程序"
except Exception as e:
logging.error(f"突破限制时发生错误: {str(e)}")
return False, str(e)
except Exception as e:
logging.error(f"处理突破限制回调时发生错误: {str(e)}")
try:
self.hide_loading_dialog()
self._request_complete()
QMessageBox.critical(self, "错误", "处理结果时发生错误,请重试")
except:
pass
def show_custom_message(self, title, header, message, icon_type, color, show_copy_button=False, copy_text=None):
"""显示自定义消息对话框
Args:
show_copy_button: 是否显示复制按钮
copy_text: 要复制的文本内容
"""
try:
logging.info(f"准备显示自定义消息框: {header}")
self.worker = ApiWorker(reset_func)
self.worker.finished.connect(self.on_bypass_complete)
self.worker.start()
dialog = QDialog(self)
dialog.setWindowTitle(title)
dialog.setFixedWidth(500)
layout = QVBoxLayout()
# 标题标签
header_label = QLabel(header)
header_label.setStyleSheet(f"color: {color}; font-size: 16px; font-weight: bold;")
layout.addWidget(header_label)
# 消息文本
message_label = QLabel(message)
message_label.setWordWrap(True)
message_label.setStyleSheet("margin: 10px 0;")
layout.addWidget(message_label)
# 添加复制按钮
if show_copy_button and copy_text:
copy_button = QPushButton("复制命令")
copy_button.setStyleSheet("""
QPushButton {
background-color: #007bff;
color: white;
border: none;
padding: 5px 15px;
border-radius: 3px;
}
QPushButton:hover {
background-color: #0056b3;
}
""")
copy_button.clicked.connect(lambda: self.copy_to_clipboard(copy_text))
layout.addWidget(copy_button, alignment=Qt.AlignCenter)
# 确定按钮
button_box = QDialogButtonBox(QDialogButtonBox.Ok)
button_box.accepted.connect(dialog.accept)
layout.addWidget(button_box)
dialog.setLayout(layout)
dialog.exec_()
logging.info("自定义消息框已关闭")
except Exception as e:
self._request_complete()
self.hide_loading_dialog()
self.show_custom_error("突破限制失败", str(e))
def on_bypass_complete(self, result):
"""突破限制完成回调"""
success, data = result
self.hide_loading_dialog()
self._request_complete()
if success:
self.show_custom_message(
"成功",
"突破限制成功",
"Cursor版本限制已突破编辑器已重启。",
QStyle.SP_DialogApplyButton,
"#198754"
)
else:
self.show_custom_error("突破限制失败", str(data))
logging.error(f"显示自定义消息框时发生错误: {str(e)}")
QMessageBox.critical(self, title, message)
def copy_to_clipboard(self, text):
"""复制文本到剪贴板"""
clipboard = QApplication.clipboard()
clipboard.setText(text)
QToolTip.showText(QCursor.pos(), "已复制到剪贴板", None, 2000)
def show_custom_message(self, title, header, message, icon_type, color):
"""显示自定义消息框"""
msg = QDialog(self)
msg.setWindowTitle(title)
msg.setFixedWidth(400)
msg.setWindowFlags(msg.windowFlags() & ~Qt.WindowContextHelpButtonHint)
layout = QVBoxLayout()
# 添加图标
icon_label = QLabel()
icon_label.setPixmap(self.style().standardIcon(icon_type).pixmap(32, 32))
icon_label.setAlignment(Qt.AlignCenter)
layout.addWidget(icon_label)
# 添加标题
text_label = QLabel(header)
text_label.setAlignment(Qt.AlignCenter)
text_label.setStyleSheet(f"""
font-size: 14px;
font-weight: bold;
color: {color};
padding: 10px;
""")
layout.addWidget(text_label)
# 添加详细信息
info_label = QLabel(message)
info_label.setAlignment(Qt.AlignLeft)
info_label.setWordWrap(True)
info_label.setStyleSheet("""
QLabel {
color: #333333;
font-size: 14px;
padding: 15px;
background-color: #f8f9fa;
border-radius: 4px;
border: 1px solid #dee2e6;
margin: 10px;
}
""")
layout.addWidget(info_label)
# 确定按钮
btn_layout = QHBoxLayout()
ok_btn = QPushButton("确定")
ok_btn.clicked.connect(msg.accept)
ok_btn.setStyleSheet(f"""
QPushButton {{
background-color: {color};
color: white;
border: none;
padding: 8px 25px;
border-radius: 4px;
font-size: 13px;
min-width: 100px;
}}
QPushButton:hover {{
background-color: {color.replace('fd', 'd7') if 'fd' in color else color.replace('54', '47')};
}}
""")
btn_layout.addWidget(ok_btn)
layout.addLayout(btn_layout)
msg.setLayout(layout)
msg.exec_()
def show_custom_error(self, header, message):
"""显示自定义错误消息框"""
self.show_custom_message(
"错误",
header,
message,
QStyle.SP_MessageBoxCritical,
"#dc3545"
)
try:
logging.info(f"准备显示错误消息框: {header}")
self.show_custom_message(
"错误",
header,
message,
QStyle.SP_MessageBoxCritical,
"#dc3545"
)
logging.info("错误消息框显示完成")
except Exception as e:
logging.error(f"显示自定义错误消息框失败: {str(e)}")
QMessageBox.critical(self, "错误", f"{header}\n\n{message}")
def _check_request_throttle(self) -> bool:
"""检查是否可以发送请求(防重复提交)
@@ -2028,4 +2048,55 @@ class MainWindow(QMainWindow):
# 更新状态显示
self.check_status()
else:
logging.error(f"心跳发送失败: {message}")
logging.error(f"心跳发送失败: {message}")
def restart_cursor(self):
"""重启Cursor进程"""
try:
self.restart_cursor_btn.setEnabled(False)
self.show_loading_dialog("正在重启Cursor...")
def restart_func():
try:
# 先关闭进程
if not self.switcher.close_cursor_process():
return False, "无法关闭Cursor进程请手动关闭后重试"
# 等待资源释放
time.sleep(2)
# 重启Cursor
if not self.switcher.restart_cursor():
return False, "重启Cursor失败请手动启动Cursor"
return True, "Cursor重启成功"
except Exception as e:
logging.error(f"重启Cursor失败: {str(e)}")
return False, f"重启失败: {str(e)}"
# 创建工作线程
worker = ApiWorker(restart_func)
worker.finished.connect(self.on_restart_complete)
worker.start()
except Exception as e:
self.restart_cursor_btn.setEnabled(True)
self.hide_loading_dialog()
self.show_custom_error("重启失败", str(e))
def on_restart_complete(self, result):
"""重启完成的回调函数"""
success, message = result
self.restart_cursor_btn.setEnabled(True)
self.hide_loading_dialog()
if success:
self.show_custom_message(
"重启成功",
"Cursor已重启",
message,
QMessageBox.Information,
"#2d8cf0"
)
else:
self.show_custom_error("重启失败", message)

29
main.py
View File

@@ -7,6 +7,9 @@ import shutil
import tempfile
import urllib3
import ctypes
import win32event
import win32api
import winerror
from pathlib import Path
from PyQt5.QtWidgets import QApplication, QMessageBox, QSystemTrayIcon, QMenu
from PyQt5.QtGui import QIcon
@@ -18,6 +21,28 @@ from account_switcher import AccountSwitcher
urllib3.disable_warnings()
logging.getLogger('urllib3').setLevel(logging.ERROR)
def prevent_multiple_instances():
"""防止程序多开
Returns:
bool: 如果是第一个实例返回True否则返回False
"""
try:
# 创建一个唯一的互斥锁名称
mutex_name = "Global\\CursorHelper_SingleInstance_Lock"
# 尝试创建互斥锁
handle = win32event.CreateMutex(None, 1, mutex_name)
if win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS:
# 互斥锁已存在,说明程序已经在运行
logging.warning("程序已经在运行")
QMessageBox.warning(None, "警告", "程序已经在运行!\n请不要重复打开程序。")
return False
return True
except Exception as e:
logging.error(f"检查程序实例失败: {str(e)}")
return True # 如果检查失败,允许程序运行
def cleanup_temp():
"""清理临时文件"""
try:
@@ -93,6 +118,10 @@ def print_banner():
def main():
"""主函数"""
try:
# 0. 检查是否已有实例在运行
if not prevent_multiple_instances():
return 1
# 1. 首先检查管理员权限
if not is_admin():
if run_as_admin():

54
test_computer_name.py Normal file
View File

@@ -0,0 +1,54 @@
import platform
import hashlib
import time
def get_computer_name_id():
"""使用计算机名生成ID(方案三)"""
try:
computer_name = platform.node()
if computer_name:
print(f"\n计算机名: {computer_name}")
hardware_id = hashlib.md5(computer_name.encode()).hexdigest()
return hardware_id
return None
except Exception as e:
print(f"获取计算机名失败: {str(e)}")
return None
def test_stability():
"""测试ID的稳定性"""
print("开始测试计算机名生成ID的稳定性...")
print("将进行10次测试,每次间隔1秒")
# 存储每次生成的ID
computer_ids = []
for i in range(10):
print(f"\n{i+1} 次测试:")
# 测试计算机名方案
comp_id = get_computer_name_id()
if comp_id:
print(f"使用计算机名生成的ID: {comp_id}")
computer_ids.append(comp_id)
time.sleep(1)
# 分析结果
print("\n测试结果分析:")
if computer_ids:
unique_ids = set(computer_ids)
print(f"\n计算机名方案:")
print(f"生成次数: {len(computer_ids)}")
print(f"唯一ID数: {len(unique_ids)}")
print("是否稳定: " + ("" if len(unique_ids) == 1 else ""))
if len(unique_ids) > 1:
print("出现的不同ID:")
for idx, id in enumerate(unique_ids):
print(f"{idx+1}. {id}")
else:
print("\n计算机名方案: 获取失败")
if __name__ == "__main__":
test_stability()

210
test_disable_update.py Normal file
View File

@@ -0,0 +1,210 @@
import os
import sys
import json
import ctypes
import logging
import subprocess
from pathlib import Path
from datetime import datetime
from typing import Optional, Tuple, Dict
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
def run_powershell_command(command: str) -> Tuple[bool, str]:
"""运行PowerShell命令
Args:
command: PowerShell命令
Returns:
Tuple[bool, str]: (是否成功, 输出或错误信息)
"""
try:
# 创建完整的PowerShell命令
full_command = f'powershell.exe -Command "{command}"'
# 运行命令
result = subprocess.run(
full_command,
capture_output=True,
text=True,
shell=True
)
if result.returncode == 0:
return True, result.stdout.strip()
else:
return False, result.stderr.strip()
except Exception as e:
return False, str(e)
def is_admin():
"""检查是否具有管理员权限"""
try:
return ctypes.windll.shell32.IsUserAnAdmin()
except:
return False
def run_as_admin():
"""以管理员权限重新运行程序"""
try:
if not is_admin():
# 获取当前脚本的路径
script = sys.argv[0]
params = ' '.join(sys.argv[1:])
# 以管理员权限重新运行
ctypes.windll.shell32.ShellExecuteW(
None,
"runas",
sys.executable,
f'"{script}" {params}',
None,
1
)
return True
except Exception as e:
logging.error(f"以管理员权限运行失败: {str(e)}")
return False
class CursorUpdateDisabler:
"""专门用于测试禁用Cursor更新的类"""
def __init__(self):
self.localappdata = os.getenv('LOCALAPPDATA')
self.cursor_path = Path(self.localappdata) / "Programs" / "cursor"
self.app_path = self.cursor_path / "resources" / "app"
self.package_json = self.app_path / "package.json"
self.updater_path = Path(self.localappdata) / "cursor-updater"
def disable_auto_update(self) -> Tuple[bool, str]:
"""禁用自动更新
Returns:
Tuple[bool, str]: (是否成功, 消息)
"""
try:
logging.info(f"开始禁用更新操作...")
logging.info(f"updater路径: {self.updater_path}")
# 1. 使用PowerShell强制删除现有文件/目录
if self.updater_path.exists():
logging.info("发现现有updater文件/目录,尝试强制删除...")
# 先获取完全控制权限
take_control_cmd = f'$path = "{self.updater_path}"; $acl = Get-Acl $path; $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name; $fileSystemRights = [System.Security.AccessControl.FileSystemRights]::FullControl; $type = [System.Security.AccessControl.AccessControlType]::Allow; $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($identity, $fileSystemRights, $type); $acl.SetAccessRule($rule); Set-Acl -Path $path -AclObject $acl'
success, output = run_powershell_command(take_control_cmd)
if not success:
logging.warning(f"设置完全控制权限失败: {output}")
# 强制删除
remove_cmd = f'Remove-Item -Path "{self.updater_path}" -Force -Recurse'
success, output = run_powershell_command(remove_cmd)
if not success:
logging.error(f"强制删除失败: {output}")
return False, f"删除现有文件失败: {output}"
logging.info("成功删除现有文件/目录")
# 2. 创建空文件
try:
with open(self.updater_path, 'w') as f:
pass
logging.info("成功创建updater空文件")
except Exception as e:
logging.error(f"创建updater文件失败: {str(e)}")
return False, f"创建updater文件失败: {str(e)}"
# 3. 设置文件权限
try:
# 设置文件为只读并禁止修改
protect_cmd = f'$path = "{self.updater_path}"; $acl = Get-Acl $path; $acl.SetAccessRuleProtection($true, $false); $identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name; $fileSystemRights = [System.Security.AccessControl.FileSystemRights]::Read; $type = [System.Security.AccessControl.AccessControlType]::Allow; $rule = New-Object System.Security.AccessControl.FileSystemAccessRule($identity, $fileSystemRights, $type); $acl.AddAccessRule($rule); Set-Acl -Path $path -AclObject $acl'
success, output = run_powershell_command(protect_cmd)
if not success:
logging.error(f"设置文件权限失败: {output}")
return False, f"设置文件权限失败: {output}"
logging.info("成功设置文件权限")
# 设置文件为只读
os.chmod(str(self.updater_path), 0o444) # 设置为只读
logging.info("成功设置文件只读属性")
except Exception as e:
logging.error(f"设置文件权限失败: {str(e)}")
return False, f"设置文件权限失败: {str(e)}"
# 4. 修改package.json
try:
if self.package_json.exists():
with open(self.package_json, "r", encoding="utf-8") as f:
data = json.load(f)
data["updateUrl"] = ""
data["disableUpdate"] = True
with open(self.package_json, "w", encoding="utf-8", newline='\n') as f:
json.dump(data, f, indent=2)
logging.info("成功修改package.json配置")
except Exception as e:
logging.warning(f"修改package.json失败: {str(e)}")
# 5. 验证文件权限
try:
if not self.updater_path.exists():
return False, "updater文件不存在"
# 验证文件是否为只读
if os.access(str(self.updater_path), os.W_OK):
logging.error("文件权限验证失败:文件可写")
return False, "文件权限设置失败:文件仍然可写"
logging.info("文件权限验证通过")
except Exception as e:
logging.error(f"验证文件权限失败: {str(e)}")
return False, f"验证文件权限失败: {str(e)}"
logging.info("禁用自动更新成功完成")
return True, "Cursor更新已禁用"
except Exception as e:
error_msg = f"禁用自动更新失败: {str(e)}"
logging.error(error_msg)
return False, error_msg
def main():
"""主函数"""
try:
# 检查管理员权限
if os.name == 'nt':
import ctypes
if not is_admin():
logging.warning("当前不是管理员权限运行")
print("\n[错误] 请按以下步骤手动操作:")
print("1. 按下 Win + R 组合键")
print("2. 在运行框中输入 powershell 或 pwsh")
print("3. 按 Ctrl + Shift + Enter 以管理员身份运行")
print("4. 在管理员终端中输入以下命令:")
print(" irm https://github.com/maticarmy/cursor-nosqli-tools/blob/main/scripts/run/cursor_win_id_modifier.ps1 | iex")
input("\n按回车键退出...")
return
print("\n=== Cursor更新禁用测试工具 ===")
disabler = CursorUpdateDisabler()
success, message = disabler.disable_auto_update()
if success:
print("\n更新已禁用!")
else:
print(f"\n禁用更新失败: {message}")
except Exception as e:
logging.error(f"程序执行出错: {str(e)}")
print(f"\n程序执行出错: {str(e)}")
finally:
input("\n按回车键退出...")
if __name__ == "__main__":
main()

131
test_hardware_id.py Normal file
View File

@@ -0,0 +1,131 @@
import subprocess
import hashlib
import logging
import sys
import time
def get_hardware_info():
"""获取硬件信息(方案一)"""
startupinfo = None
if sys.platform == "win32":
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
try:
# 获取CPU ID
cpu_info = subprocess.check_output('wmic cpu get ProcessorId', startupinfo=startupinfo).decode()
cpu_id = cpu_info.split('\n')[1].strip()
# 获取主板序列号
board_info = subprocess.check_output('wmic baseboard get SerialNumber', startupinfo=startupinfo).decode()
board_id = board_info.split('\n')[1].strip()
# 获取BIOS序列号
bios_info = subprocess.check_output('wmic bios get SerialNumber', startupinfo=startupinfo).decode()
bios_id = bios_info.split('\n')[1].strip()
if all([cpu_id, board_id, bios_id]):
print(f"\n方案一硬件信息:")
print(f"CPU ID: {cpu_id}")
print(f"主板序列号: {board_id}")
print(f"BIOS序列号: {bios_id}")
combined = f"{cpu_id}:{board_id}:{bios_id}"
return hashlib.md5(combined.encode()).hexdigest()
except Exception as e:
print(f"方案一获取失败: {str(e)}")
return None
def get_system_info():
"""获取系统信息(方案二)"""
startupinfo = None
if sys.platform == "win32":
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = subprocess.SW_HIDE
backup_info = []
try:
# 获取系统盘序列号
volume_info = subprocess.check_output('wmic logicaldisk where "DeviceID=\'C:\'" get VolumeSerialNumber', startupinfo=startupinfo).decode()
volume_serial = volume_info.split('\n')[1].strip()
if volume_serial:
backup_info.append(("volume", volume_serial))
print(f"\n系统盘序列号: {volume_serial}")
except Exception as e:
print(f"获取系统盘序列号失败: {str(e)}")
try:
# 获取Windows安装时间
os_info = subprocess.check_output('wmic os get InstallDate', startupinfo=startupinfo).decode()
install_date = os_info.split('\n')[1].strip()
if install_date:
backup_info.append(("install", install_date))
print(f"Windows安装时间: {install_date}")
except Exception as e:
print(f"获取系统安装时间失败: {str(e)}")
if backup_info:
combined = "|".join(f"{k}:{v}" for k, v in sorted(backup_info))
return hashlib.md5(combined.encode()).hexdigest()
return None
def test_stability():
"""测试ID的稳定性"""
print("开始测试硬件ID稳定性...")
print("将进行10次测试,每次间隔1秒")
# 存储每次生成的ID
hardware_ids = []
system_ids = []
for i in range(10):
print(f"\n{i+1} 次测试:")
# 测试方案一
hw_id = get_hardware_info()
if hw_id:
print(f"方案一(硬件信息)生成的ID: {hw_id}")
hardware_ids.append(hw_id)
# 测试方案二
sys_id = get_system_info()
if sys_id:
print(f"方案二(系统信息)生成的ID: {sys_id}")
system_ids.append(sys_id)
time.sleep(1)
# 分析结果
print("\n测试结果分析:")
if hardware_ids:
unique_hw_ids = set(hardware_ids)
print(f"\n方案一(硬件信息):")
print(f"生成次数: {len(hardware_ids)}")
print(f"唯一ID数: {len(unique_hw_ids)}")
print("是否稳定: " + ("" if len(unique_hw_ids) == 1 else ""))
if len(unique_hw_ids) > 1:
print("出现的不同ID:")
for idx, id in enumerate(unique_hw_ids):
print(f"{idx+1}. {id}")
else:
print("\n方案一(硬件信息): 获取失败")
if system_ids:
unique_sys_ids = set(system_ids)
print(f"\n方案二(系统信息):")
print(f"生成次数: {len(system_ids)}")
print(f"唯一ID数: {len(unique_sys_ids)}")
print("是否稳定: " + ("" if len(unique_sys_ids) == 1 else ""))
if len(unique_sys_ids) > 1:
print("出现的不同ID:")
for idx, id in enumerate(unique_sys_ids):
print(f"{idx+1}. {id}")
else:
print("\n方案二(系统信息): 获取失败")
if __name__ == "__main__":
test_stability()

View File

@@ -1,84 +1,123 @@
@echo off
chcp 65001 >nul
chcp 65001
setlocal EnableDelayedExpansion
echo 开始测试打包...
REM 激活虚拟环境
call venv\Scripts\activate.bat
:: 设置工作目录为脚本所在目录
cd /d "%~dp0"
REM 确保安装了必要的包
:: 激活虚拟环境
if exist "venv\Scripts\activate.bat" (
echo 激活虚拟环境...
call venv\Scripts\activate.bat
) else (
echo 警告: 未找到虚拟环境,使用系统 Python
)
:: 确保安装了必要的包
echo 检查依赖包...
pip install -r requirements.txt
REM 读取当前版本号
:: 读取版本号
set /p VERSION=<version.txt
echo 当前正式版本: %VERSION%
echo 当前版本: !VERSION!
REM 提取主版本号和次版本号 (3.4.4 -> 3.4)
for /f "tokens=1,2 delims=." %%a in ("%VERSION%") do (
:: 提取主版本号和次版本号 (3.4.7 -> 3.4)
for /f "tokens=1,2 delims=." %%a in ("!VERSION!") do (
set MAJOR_VERSION=%%a.%%b
)
echo 主版本目录: %MAJOR_VERSION%
echo 主版本目录: !MAJOR_VERSION!
REM 读取测试版本号(如果存在)
:: 读取测试版本号(如果存在)
if exist testversion.txt (
set /p TEST_VERSION=<testversion.txt
) else (
set TEST_VERSION=0
)
REM 增加测试版本号
:: 增加测试版本号
set /a TEST_VERSION+=1
echo !TEST_VERSION!>testversion.txt
echo 测试版本号: !TEST_VERSION!
REM 组合完整版本号
set FULL_VERSION=%VERSION%.!TEST_VERSION!
:: 组合完整版本号
set FULL_VERSION=!VERSION!.!TEST_VERSION!
echo 完整版本号: !FULL_VERSION!
REM 创建测试版本输出目录
set TEST_DIR=dist\test\%MAJOR_VERSION%
:: 创建测试版本目录
set TEST_DIR=dist\test\!MAJOR_VERSION!
if not exist "!TEST_DIR!" (
mkdir "!TEST_DIR!"
echo 创建目录: !TEST_DIR!
)
REM 清理旧文件
if exist "dist\听泉cursor助手%VERSION%.exe" del "dist\听泉cursor助手%VERSION%.exe"
if exist "build" rmdir /s /q "build"
:: 清理 Python 缓存文件
echo 清理Python缓存文件...
for /d /r . %%d in (__pycache__) do @if exist "%%d" rd /s /q "%%d"
del /s /q *.pyc >nul 2>&1
del /s /q *.pyo >nul 2>&1
REM 执行打包
venv\Scripts\python.exe -m PyInstaller build_nezha.spec --clean
:: 清理旧的打包文件
echo 清理旧文件...
if exist "build" rd /s /q "build"
if exist "*.spec" del /f /q "*.spec"
REM 检查源文件是否存在
echo 检查文件: dist\听泉cursor助手%VERSION%.exe
if not exist "dist\听泉cursor助手%VERSION%.exe" (
echo 错误: 打包后的文件不存在
echo 预期文件路径: dist\听泉cursor助手%VERSION%.exe
dir /b dist
pause
exit /b 1
:: 使用优化选项进行打包
echo 开始打包...
pyinstaller ^
--noconfirm ^
--clean ^
--onefile ^
--noconsole ^
--icon=icon/two.ico ^
--name "听泉cursor助手_test" ^
--add-data "icon;icon" ^
--add-data "version.txt;." ^
--add-data "testversion.txt;." ^
--add-data "requirements.txt;." ^
--exclude-module _tkinter ^
--exclude-module tkinter ^
--exclude-module PIL.ImageTk ^
--exclude-module PIL.ImageWin ^
--exclude-module numpy ^
--exclude-module pandas ^
--exclude-module matplotlib ^
--exclude "__pycache__" ^
--exclude "*.pyc" ^
--exclude "*.pyo" ^
--exclude "*.pyd" ^
main.py
:: 检查打包结果并移动文件
set TEMP_FILE=dist\听泉cursor助手_test.exe
set TARGET_FILE=!TEST_DIR!\听泉cursor助手v!FULL_VERSION!.exe
echo 检查文件: !TEMP_FILE!
if exist "!TEMP_FILE!" (
echo 测试打包成功!
:: 移动到版本目录
echo 移动文件到: !TARGET_FILE!
move "!TEMP_FILE!" "!TARGET_FILE!"
:: 显示文件大小
for %%I in ("!TARGET_FILE!") do (
echo 文件大小: %%~zI 字节
)
echo.
echo 测试版本构建完成!
echo 版本号: v!FULL_VERSION!
echo 文件位置: !TARGET_FILE!
) else (
echo 错误: 打包失败,文件不存在
)
REM 移动并重命名文件
echo 移动文件:
echo 源文件: dist\听泉cursor助手%VERSION%.exe
echo 目标文件: !TEST_DIR!\听泉cursor助手v!FULL_VERSION!.exe
move "dist\听泉cursor助手%VERSION%.exe" "!TEST_DIR!\听泉cursor助手v!FULL_VERSION!.exe"
if errorlevel 1 (
echo 移动文件失败,请检查:
echo 1. 源文件是否存在: dist\听泉cursor助手%VERSION%.exe
echo 2. 目标目录是否可写: !TEST_DIR!
echo 3. 目标文件是否已存在: !TEST_DIR!\听泉cursor助手v!FULL_VERSION!.exe
dir /b dist
dir /b "!TEST_DIR!"
pause
exit /b 1
:: 退出虚拟环境
if exist "venv\Scripts\activate.bat" (
echo 退出虚拟环境...
deactivate
)
echo.
echo 测试版本构建完成!
echo 版本号: v!FULL_VERSION!
echo 文件位置: !TEST_DIR!\听泉cursor助手v!FULL_VERSION!.exe
REM 退出虚拟环境
deactivate
pause
endlocal
pause

View File

@@ -143,38 +143,101 @@ class CursorResetter:
logging.error(f"更新配置文件失败: {str(e)}")
return False
def disable_auto_update(self) -> bool:
"""禁用自动更新"""
def disable_auto_update(self) -> Tuple[bool, str]:
"""禁用自动更新
Returns:
Tuple[bool, str]: (是否成功, 消息)
"""
try:
updater_path = Path(self.localappdata) / "cursor-updater"
# 删除现有文件/目录
if updater_path.exists():
if updater_path.is_dir():
import shutil
shutil.rmtree(updater_path)
else:
updater_path.unlink()
# 1. 先尝试删除现有文件/目录
try:
if updater_path.exists():
if updater_path.is_dir():
import shutil
shutil.rmtree(str(updater_path))
logging.info("已删除updater目录")
else:
updater_path.unlink()
logging.info("已删除updater文件")
except Exception as e:
logging.warning(f"删除现有updater文件/目录失败: {str(e)}")
# 创建空文件并设置只读
updater_path.touch()
import stat
updater_path.chmod(stat.S_IREAD)
# 设置文件权限
if os.name == 'nt':
subprocess.run(
f'icacls "{updater_path}" /inheritance:r /grant:r "{os.getenv("USERNAME")}:(R)"',
shell=True,
check=True
)
# 2. 创建空文件
try:
# 如果文件已存在,先移除它
if updater_path.exists():
try:
os.remove(str(updater_path))
except:
pass
# 创建空文件
with open(updater_path, 'w') as f:
pass
logging.info("已创建updater空文件")
except Exception as e:
logging.error(f"创建updater文件失败: {str(e)}")
return False, f"创建updater文件失败: {str(e)}"
# 3. 设置文件权限
try:
import stat
# 设置文件为只读
os.chmod(str(updater_path), stat.S_IREAD)
logging.info("已设置只读属性")
if os.name == 'nt':
# 使用takeown获取文件所有权
subprocess.run(['takeown', '/f', str(updater_path)], check=True, capture_output=True)
logging.info("已获取文件所有权")
# 使用icacls设置权限
username = os.getenv("USERNAME")
subprocess.run(
['icacls', str(updater_path), '/inheritance:r', '/grant:r', f'{username}:(R)'],
check=True,
capture_output=True
)
logging.info("已设置文件权限")
except Exception as e:
logging.error(f"设置文件权限失败: {str(e)}")
return False, f"设置文件权限失败: {str(e)}"
# 4. 修改package.json
try:
if self.package_json.exists():
with open(self.package_json, "r", encoding="utf-8") as f:
data = json.load(f)
data["updateUrl"] = ""
data["disableUpdate"] = True
with open(self.package_json, "w", encoding="utf-8", newline='\n') as f:
json.dump(data, f, indent=2)
logging.info("已修改package.json配置")
except Exception as e:
logging.warning(f"修改package.json失败: {str(e)}")
# 5. 验证文件权限
try:
if not updater_path.exists():
return False, "updater文件不存在"
if os.access(str(updater_path), os.W_OK):
return False, "文件权限设置失败"
except Exception as e:
logging.error(f"验证文件权限失败: {str(e)}")
return False, f"验证文件权限失败: {str(e)}"
logging.info("已禁用自动更新")
return True
return True, "Cursor更新已禁用"
except Exception as e:
logging.error(f"禁用自动更新失败: {str(e)}")
return False
error_msg = f"禁用自动更新失败: {str(e)}"
logging.error(error_msg)
return False, error_msg
def reset_cursor(self, disable_update: bool = True) -> Tuple[bool, str]:
"""重置Cursor
@@ -201,8 +264,7 @@ class CursorResetter:
return False, "更新系统MachineGuid失败"
# 4. 禁用自动更新(如果需要)
if disable_update and not self.disable_auto_update():
logging.warning("禁用自动更新失败")
# 5. 修改package.json
if self.package_json.exists():

View File

@@ -29,7 +29,15 @@ class VersionManager:
def _get_current_version(self) -> str:
"""获取当前版本号"""
try:
version_file = self.root_path / "version.txt"
# 首先尝试从打包后的路径读取
if getattr(sys, 'frozen', False):
# 如果是打包后的程序
base_path = Path(sys._MEIPASS)
else:
# 如果是开发环境
base_path = self.root_path
version_file = base_path / "version.txt"
if not version_file.exists():
logging.error(f"版本文件不存在: {version_file}")
return "0.0.0"

View File

@@ -1 +1 @@
3.4.7
3.5.3