diff --git a/__pycache__/common_utils.cpython-312.pyc b/__pycache__/common_utils.cpython-312.pyc index 32e7117..db7aba5 100644 Binary files a/__pycache__/common_utils.cpython-312.pyc and b/__pycache__/common_utils.cpython-312.pyc differ diff --git a/__pycache__/cursor_token_refresher.cpython-312.pyc b/__pycache__/cursor_token_refresher.cpython-312.pyc index 0aa17b5..6573607 100644 Binary files a/__pycache__/cursor_token_refresher.cpython-312.pyc and b/__pycache__/cursor_token_refresher.cpython-312.pyc differ diff --git a/__pycache__/machine_resetter.cpython-312.pyc b/__pycache__/machine_resetter.cpython-312.pyc index ee26c7d..86fd17e 100644 Binary files a/__pycache__/machine_resetter.cpython-312.pyc and b/__pycache__/machine_resetter.cpython-312.pyc differ diff --git a/__pycache__/update_disabler.cpython-312.pyc b/__pycache__/update_disabler.cpython-312.pyc index c95f316..cacfb78 100644 Binary files a/__pycache__/update_disabler.cpython-312.pyc and b/__pycache__/update_disabler.cpython-312.pyc differ diff --git a/build.bat b/build.bat index e5795cc..7cea7c2 100644 --- a/build.bat +++ b/build.bat @@ -27,7 +27,21 @@ set VERSION=!VERSION: =! for /f "tokens=1-3 delims=." %%a in ("!VERSION!") do ( set MAJOR=%%a set MINOR=%%b - set /a PATCH=%%c+1 + set PATCH=%%c + + :: 检查PATCH是否为9,如果是则增加MINOR版本并重置PATCH为0 + if "!PATCH!"=="9" ( + set /a MINOR=!MINOR!+1 + set PATCH=0 + + :: 检查MINOR是否为10,如果是则增加MAJOR版本并重置MINOR为0 + if "!MINOR!"=="10" ( + set /a MAJOR=!MAJOR!+1 + set MINOR=0 + ) + ) else ( + set /a PATCH=!PATCH!+1 + ) ) :: 组合新版本号 diff --git a/common_utils.py b/common_utils.py index 87ec8d2..9aab94b 100644 --- a/common_utils.py +++ b/common_utils.py @@ -400,7 +400,7 @@ def activate_device(machine_id: str, activation_code: str) -> Tuple[bool, str, O return True, f"激活成功,剩余{state_data['days_left']}天", state_data elif result.get("code") == 400: - error_msg = result.get("msg", "激活码无效或已被使用") + error_msg = result.get("msg", "激活码已使用完,请勿重复使用") _logger.warning(f"激活失败: {error_msg}") return False, error_msg, None diff --git a/config.py b/config.py index be1eb61..2d8313d 100644 --- a/config.py +++ b/config.py @@ -6,7 +6,8 @@ class Config: def __init__(self): """初始化配置""" # API基础URL - self.base_url = "https://cursorapi.nosqli.com" + self.base_url = "https://api.cursorpro.com.cn" + #self.base_url = "https://cursorapi.nosqli.com" # API端点 self.api_endpoints = { diff --git a/cursor_db_export.py b/cursor_db_export.py new file mode 100644 index 0000000..87ace9c --- /dev/null +++ b/cursor_db_export.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sqlite3 +import os +import sys +import json +import csv +import datetime + +def export_cursor_tables(): + """导出Cursor数据库中的ItemTable和cursorDiskKV表""" + print("Cursor 数据库表导出工具") + print("-" * 50) + + # 获取数据库路径 + if sys.platform == "win32": + appdata = os.getenv("APPDATA") + if appdata is None: + print("错误: APPDATA 环境变量未设置!") + return + db_path = os.path.join(appdata, "Cursor", "User", "globalStorage", "state.vscdb") + else: + print("目前只支持Windows系统") + return + + if not os.path.exists(db_path): + print(f"错误: Cursor数据库文件不存在: {db_path}") + return + + print(f"使用Cursor数据库: {db_path}") + + # 创建导出目录 + timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") + export_dir = f"cursor_db_export_{timestamp}" + os.makedirs(export_dir, exist_ok=True) + print(f"将导出数据到目录: {export_dir}") + + try: + # 连接到数据库 + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + tables_to_export = ["ItemTable", "cursorDiskKV"] + + for table_name in tables_to_export: + print(f"\n正在导出表 '{table_name}'...") + + # 检查表是否存在 + cursor.execute(f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}';") + if not cursor.fetchone(): + print(f"表 '{table_name}' 不存在!") + continue + + # 获取表的列名 + cursor.execute(f"PRAGMA table_info({table_name});") + columns = cursor.fetchall() + col_names = [col[1] for col in columns] + + # 获取所有数据 + cursor.execute(f"SELECT * FROM {table_name};") + rows = cursor.fetchall() + + if not rows: + print(f"表 '{table_name}' 中没有数据") + continue + + record_count = len(rows) + print(f"找到 {record_count} 条记录") + + # 导出为JSON + json_file = os.path.join(export_dir, f"{table_name}.json") + with open(json_file, 'w', encoding='utf-8') as f: + json_data = [] + for row in rows: + row_dict = {} + for i, col_name in enumerate(col_names): + # 处理特殊类型 + if isinstance(row[i], bytes): + try: + # 尝试解码为JSON + row_dict[col_name] = json.loads(row[i]) + except: + # 如果失败则使用hex表示 + row_dict[col_name] = row[i].hex() + else: + row_dict[col_name] = row[i] + json_data.append(row_dict) + json.dump(json_data, f, ensure_ascii=False, indent=2) + print(f"已导出JSON: {json_file}") + + # 导出为CSV + csv_file = os.path.join(export_dir, f"{table_name}.csv") + with open(csv_file, 'w', encoding='utf-8', newline='') as f: + csv_writer = csv.writer(f) + csv_writer.writerow(col_names) # 写入表头 + for row in rows: + # 处理每个字段,确保CSV可以正确处理 + processed_row = [] + for item in row: + if isinstance(item, bytes): + try: + # 尝试解码为字符串 + processed_row.append(str(item)) + except: + # 如果失败则使用hex表示 + processed_row.append(item.hex()) + else: + processed_row.append(item) + csv_writer.writerow(processed_row) + print(f"已导出CSV: {csv_file}") + + # 导出为SQL插入语句 + sql_file = os.path.join(export_dir, f"{table_name}.sql") + with open(sql_file, 'w', encoding='utf-8') as f: + f.write(f"-- 导出自 {db_path}\n") + f.write(f"-- 表: {table_name}\n") + f.write(f"-- 时间: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n") + f.write(f"-- 记录数: {record_count}\n\n") + + # 获取表结构 + cursor.execute(f"SELECT sql FROM sqlite_master WHERE type='table' AND name='{table_name}';") + table_sql = cursor.fetchone()[0] + f.write(f"{table_sql};\n\n") + + # 写入INSERT语句 + for row in rows: + values = [] + for item in row: + if item is None: + values.append("NULL") + elif isinstance(item, (int, float)): + values.append(str(item)) + elif isinstance(item, bytes): + hex_value = ''.join([f'\\x{b:02x}' for b in item]) + values.append(f"X'{item.hex()}'") + else: + # 转义字符串中的引号 + item_str = str(item).replace("'", "''") + values.append(f"'{item_str}'") + + f.write(f"INSERT INTO {table_name} ({', '.join(col_names)}) VALUES ({', '.join(values)});\n") + print(f"已导出SQL: {sql_file}") + + # 如果是cursorDiskKV表,尝试解析其中的JSON数据 + if table_name == "cursorDiskKV": + json_parsed_file = os.path.join(export_dir, f"{table_name}_parsed.json") + with open(json_parsed_file, 'w', encoding='utf-8') as f: + parsed_data = {} + for row in rows: + # 通常cursorDiskKV表有key和value两列 + key = row[0] if isinstance(row[0], str) else str(row[0]) + value = row[1] + + # 尝试解析value为JSON + if isinstance(value, bytes): + try: + parsed_value = json.loads(value) + parsed_data[key] = parsed_value + except: + parsed_data[key] = value.hex() + elif isinstance(value, str): + try: + parsed_value = json.loads(value) + parsed_data[key] = parsed_value + except: + parsed_data[key] = value + else: + parsed_data[key] = value + + json.dump(parsed_data, f, ensure_ascii=False, indent=2) + print(f"已导出解析后的JSON: {json_parsed_file}") + + conn.close() + print(f"\n导出完成!所有数据已保存到目录: {os.path.abspath(export_dir)}") + + except sqlite3.Error as e: + print(f"SQLite 错误: {e}") + except Exception as e: + print(f"发生错误: {e}") + import traceback + traceback.print_exc() + +if __name__ == "__main__": + export_cursor_tables() \ No newline at end of file diff --git a/cursor_db_viewer.py b/cursor_db_viewer.py new file mode 100644 index 0000000..1e5f0b3 --- /dev/null +++ b/cursor_db_viewer.py @@ -0,0 +1,182 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sqlite3 +import os +import sys + +def view_cursor_db(): + """查看Cursor数据库内容""" + print("Cursor SQLite3 数据库查看器") + print("-" * 50) + + # 获取数据库路径 + if sys.platform == "win32": + appdata = os.getenv("APPDATA") + if appdata is None: + print("错误: APPDATA 环境变量未设置!") + return + db_path = os.path.join(appdata, "Cursor", "User", "globalStorage", "state.vscdb") + else: + print("目前只支持Windows系统") + return + + if not os.path.exists(db_path): + print(f"错误: Cursor数据库文件不存在: {db_path}") + return + + print(f"使用Cursor数据库: {db_path}") + + try: + # 连接到数据库 + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + while True: + print("\n" + "-" * 50) + print("可用操作:") + print("1. 显示所有表") + print("2. 查看表结构") + print("3. 查看表数据") + print("4. 执行自定义SQL查询") + print("5. 退出") + + choice = input("\n请选择操作 (1-5): ").strip() + + if choice == "1": + # 显示所有表 + cursor.execute("SELECT name FROM sqlite_master WHERE type='table';") + tables = cursor.fetchall() + + if not tables: + print("数据库中没有表") + else: + print(f"\n数据库中的表 ({len(tables)}):") + for i, table in enumerate(tables): + print(f"{i+1}. {table[0]}") + + elif choice == "2": + # 查看表结构 + table_name = input("请输入表名: ").strip() + cursor.execute(f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}';") + if not cursor.fetchone(): + print(f"表 '{table_name}' 不存在!") + continue + + cursor.execute(f"PRAGMA table_info({table_name});") + columns = cursor.fetchall() + print(f"\n表 '{table_name}' 的结构:") + print("-" * 50) + print(f"{'序号':5} {'列名':20} {'类型':15} {'是否可空':10} {'默认值':15}") + print("-" * 70) + for col in columns: + col_id, name, type_, notnull, default_val, pk = col + print(f"{col_id:5} {name:20} {type_:15} {'否' if notnull else '是':10} {str(default_val)[:15]:15}") + + elif choice == "3": + # 查看表数据 + table_name = input("请输入表名: ").strip() + cursor.execute(f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}';") + if not cursor.fetchone(): + print(f"表 '{table_name}' 不存在!") + continue + + limit = input("显示多少条记录 (默认10): ").strip() or "10" + try: + limit = int(limit) + except ValueError: + print("请输入有效数字!") + continue + + cursor.execute(f"SELECT COUNT(*) FROM {table_name};") + total = cursor.fetchone()[0] + print(f"\n表 '{table_name}' 共有 {total} 条记录,显示前 {limit} 条:") + + if total == 0: + print("表中没有数据") + continue + + # 获取列名 + cursor.execute(f"PRAGMA table_info({table_name});") + columns = cursor.fetchall() + col_names = [col[1] for col in columns] + + # 显示数据 + cursor.execute(f"SELECT * FROM {table_name} LIMIT {limit};") + rows = cursor.fetchall() + + # 打印列名 + header = " | ".join(col_names) + print("\n" + header) + print("-" * len(header)) + + # 打印数据 + for row in rows: + row_str = [] + for item in row: + item_str = str(item) + if len(item_str) > 20: + item_str = item_str[:17] + "..." + row_str.append(item_str) + print(" | ".join(row_str)) + + elif choice == "4": + # 执行自定义SQL + sql = input("请输入SQL查询语句: ").strip() + if not sql: + continue + + try: + cursor.execute(sql) + if sql.lower().startswith(("select", "pragma")): + rows = cursor.fetchall() + if not rows: + print("查询返回0条结果") + continue + + # 获取列名 + col_names = [description[0] for description in cursor.description] + + # 打印列名 + header = " | ".join(col_names) + print("\n" + header) + print("-" * len(header)) + + # 最多显示50条 + display_rows = rows[:50] + for row in display_rows: + row_str = [] + for item in row: + item_str = str(item) + if len(item_str) > 20: + item_str = item_str[:17] + "..." + row_str.append(item_str) + print(" | ".join(row_str)) + + if len(rows) > 50: + print(f"...(还有 {len(rows)-50} 条记录)") + + print(f"\n总共 {len(rows)} 条记录") + else: + conn.commit() + print("查询执行成功") + except sqlite3.Error as e: + print(f"SQL错误: {e}") + + elif choice == "5": + # 退出 + break + + else: + print("无效选择,请输入1-5之间的数字") + + conn.close() + print("\n已关闭数据库连接。再见!") + + except sqlite3.Error as e: + print(f"SQLite 错误: {e}") + except Exception as e: + print(f"发生错误: {e}") + +if __name__ == "__main__": + view_cursor_db() \ No newline at end of file diff --git a/cursor_token_refresher.py b/cursor_token_refresher.py index cbbe4e1..179fec1 100644 --- a/cursor_token_refresher.py +++ b/cursor_token_refresher.py @@ -37,7 +37,8 @@ class CursorTokenRefresher: """ updates = [] # 登录状态 - updates.append(("cursorAuth/cachedSignUpType", "Auth_0")) + updates.append(("cursorAuth/cachedSignUpType", "Google")) + updates.append(("cursorAuth/stripeMembershipType", "free")) if email is not None: updates.append(("cursorAuth/cachedEmail", email)) @@ -116,7 +117,8 @@ class CursorTokenRefresher: refresh_token = account_data.get("refresh_token") expire_time = account_data.get("expire_time") days_left = account_data.get("days_left") - + password = account_data.get("password") + if not all([email, access_token, refresh_token]): return False, "获取账号信息不完整", None @@ -126,6 +128,7 @@ class CursorTokenRefresher: refresh_token=refresh_token): account_info = { "email": email, + "password": password, "expire_time": expire_time, "days_left": days_left } diff --git a/cursor_win_id_modifier.ps1 b/cursor_win_id_modifier.ps1 new file mode 100644 index 0000000..77ae2f2 --- /dev/null +++ b/cursor_win_id_modifier.ps1 @@ -0,0 +1,570 @@ +# 设置输出编码为 UTF-8 +$OutputEncoding = [System.Text.Encoding]::UTF8 +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +# 颜色定义 +$RED = "`e[31m" +$GREEN = "`e[32m" +$YELLOW = "`e[33m" +$BLUE = "`e[34m" +$NC = "`e[0m" + +# 配置文件路径 +$STORAGE_FILE = "$env:APPDATA\Cursor\User\globalStorage\storage.json" +$BACKUP_DIR = "$env:APPDATA\Cursor\User\globalStorage\backups" + +# 检查管理员权限 +function Test-Administrator { + $user = [Security.Principal.WindowsIdentity]::GetCurrent() + $principal = New-Object Security.Principal.WindowsPrincipal($user) + return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) +} + +if (-not (Test-Administrator)) { + Write-Host "$RED[错误]$NC 请以管理员身份运行此脚本" + Write-Host "请右键点击脚本,选择'以管理员身份运行'" + Read-Host "按回车键退出" + exit 1 +} + +# 显示 Logo +Clear-Host +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 "$BLUE================================$NC" +Write-Host "" + +# 获取并显示 Cursor 版本 +function Get-CursorVersion { + try { + # 主要检测路径 + $packagePath = "$env:LOCALAPPDATA\Programs\cursor\resources\app\package.json" + + if (Test-Path $packagePath) { + $packageJson = Get-Content $packagePath -Raw | ConvertFrom-Json + if ($packageJson.version) { + Write-Host "$GREEN[信息]$NC 当前安装的 Cursor 版本: v$($packageJson.version)" + return $packageJson.version + } + } + + # 备用路径检测 + $altPath = "$env:LOCALAPPDATA\cursor\resources\app\package.json" + if (Test-Path $altPath) { + $packageJson = Get-Content $altPath -Raw | ConvertFrom-Json + if ($packageJson.version) { + Write-Host "$GREEN[信息]$NC 当前安装的 Cursor 版本: v$($packageJson.version)" + return $packageJson.version + } + } + + Write-Host "$YELLOW[警告]$NC 无法检测到 Cursor 版本" + Write-Host "$YELLOW[提示]$NC 请确保 Cursor 已正确安装" + return $null + } + catch { + Write-Host "$RED[错误]$NC 获取 Cursor 版本失败: $_" + return $null + } +} + +# 获取并显示版本信息 +$cursorVersion = Get-CursorVersion +Write-Host "" + +Write-Host "$YELLOW[重要提示]$NC 最新的 0.45.x (以支持)" +Write-Host "" + +# 检查并关闭 Cursor 进程 +Write-Host "$GREEN[信息]$NC 检查 Cursor 进程..." + +function Get-ProcessDetails { + param($processName) + Write-Host "$BLUE[调试]$NC 正在获取 $processName 进程详细信息:" + Get-WmiObject Win32_Process -Filter "name='$processName'" | + Select-Object ProcessId, ExecutablePath, CommandLine | + Format-List +} + +# 定义最大重试次数和等待时间 +$MAX_RETRIES = 5 +$WAIT_TIME = 1 + +# 处理进程关闭 +function Close-CursorProcess { + param($processName) + + $process = Get-Process -Name $processName -ErrorAction SilentlyContinue + if ($process) { + Write-Host "$YELLOW[警告]$NC 发现 $processName 正在运行" + Get-ProcessDetails $processName + + Write-Host "$YELLOW[警告]$NC 尝试关闭 $processName..." + Stop-Process -Name $processName -Force + + $retryCount = 0 + while ($retryCount -lt $MAX_RETRIES) { + $process = Get-Process -Name $processName -ErrorAction SilentlyContinue + if (-not $process) { break } + + $retryCount++ + if ($retryCount -ge $MAX_RETRIES) { + Write-Host "$RED[错误]$NC 在 $MAX_RETRIES 次尝试后仍无法关闭 $processName" + Get-ProcessDetails $processName + Write-Host "$RED[错误]$NC 请手动关闭进程后重试" + Read-Host "按回车键退出" + exit 1 + } + Write-Host "$YELLOW[警告]$NC 等待进程关闭,尝试 $retryCount/$MAX_RETRIES..." + Start-Sleep -Seconds $WAIT_TIME + } + Write-Host "$GREEN[信息]$NC $processName 已成功关闭" + } +} + +# 关闭所有 Cursor 进程 +Close-CursorProcess "Cursor" +Close-CursorProcess "cursor" + +# 创建备份目录 +if (-not (Test-Path $BACKUP_DIR)) { + New-Item -ItemType Directory -Path $BACKUP_DIR | Out-Null +} + +# 备份现有配置 +if (Test-Path $STORAGE_FILE) { + Write-Host "$GREEN[信息]$NC 正在备份配置文件..." + $backupName = "storage.json.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')" + Copy-Item $STORAGE_FILE "$BACKUP_DIR\$backupName" +} + +# 生成新的 ID +Write-Host "$GREEN[信息]$NC 正在生成新的 ID..." + +# 在颜色定义后添加此函数 +function Get-RandomHex { + param ( + [int]$length + ) + + $bytes = New-Object byte[] ($length) + $rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::new() + $rng.GetBytes($bytes) + $hexString = [System.BitConverter]::ToString($bytes) -replace '-','' + $rng.Dispose() + return $hexString +} + +# 改进 ID 生成函数 +function New-StandardMachineId { + $template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" + $result = $template -replace '[xy]', { + param($match) + $r = [Random]::new().Next(16) + $v = if ($match.Value -eq "x") { $r } else { ($r -band 0x3) -bor 0x8 } + return $v.ToString("x") + } + return $result +} + +# 在生成 ID 时使用新函数 +$MAC_MACHINE_ID = New-StandardMachineId +$UUID = [System.Guid]::NewGuid().ToString() +# 将 auth0|user_ 转换为字节数组的十六进制 +$prefixBytes = [System.Text.Encoding]::UTF8.GetBytes("auth0|user_") +$prefixHex = -join ($prefixBytes | ForEach-Object { '{0:x2}' -f $_ }) +# 生成32字节(64个十六进制字符)的随机数作为 machineId 的随机部分 +$randomPart = Get-RandomHex -length 32 +$MACHINE_ID = "$prefixHex$randomPart" +$SQM_ID = "{$([System.Guid]::NewGuid().ToString().ToUpper())}" + +# 在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 { + # 先检查注册表路径是否存在 + $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 + + 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 $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 注册表操作失败:$($_.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 + } +} + +# 创建或更新配置文件 +Write-Host "$GREEN[信息]$NC 正在更新配置..." + +try { + # 检查配置文件是否存在 + if (-not (Test-Path $STORAGE_FILE)) { + Write-Host "$RED[错误]$NC 未找到配置文件: $STORAGE_FILE" + Write-Host "$YELLOW[提示]$NC 请先安装并运行一次 Cursor 后再使用此脚本" + Read-Host "按回车键退出" + exit 1 + } + + # 读取现有配置文件 + try { + $originalContent = Get-Content $STORAGE_FILE -Raw -Encoding UTF8 + + # 将 JSON 字符串转换为 PowerShell 对象 + $config = $originalContent | ConvertFrom-Json + + # 备份当前值 + $oldValues = @{ + 'machineId' = $config.'telemetry.machineId' + 'macMachineId' = $config.'telemetry.macMachineId' + 'devDeviceId' = $config.'telemetry.devDeviceId' + 'sqmId' = $config.'telemetry.sqmId' + } + + # 更新特定的值 + $config.'telemetry.machineId' = $MACHINE_ID + $config.'telemetry.macMachineId' = $MAC_MACHINE_ID + $config.'telemetry.devDeviceId' = $UUID + $config.'telemetry.sqmId' = $SQM_ID + + # 将更新后的对象转换回 JSON 并保存 + $updatedJson = $config | ConvertTo-Json -Depth 10 + [System.IO.File]::WriteAllText( + [System.IO.Path]::GetFullPath($STORAGE_FILE), + $updatedJson, + [System.Text.Encoding]::UTF8 + ) + Write-Host "$GREEN[信息]$NC 成功更新配置文件" + } catch { + # 如果出错,尝试恢复原始内容 + if ($originalContent) { + [System.IO.File]::WriteAllText( + [System.IO.Path]::GetFullPath($STORAGE_FILE), + $originalContent, + [System.Text.Encoding]::UTF8 + ) + } + throw "处理 JSON 失败: $_" + } + # 直接执行更新 MachineGuid,不再询问 + Update-MachineGuid + # 显示结果 + Write-Host "" + Write-Host "$GREEN[信息]$NC 已更新配置:" + Write-Host "$BLUE[调试]$NC machineId: $MACHINE_ID" + Write-Host "$BLUE[调试]$NC macMachineId: $MAC_MACHINE_ID" + Write-Host "$BLUE[调试]$NC devDeviceId: $UUID" + Write-Host "$BLUE[调试]$NC sqmId: $SQM_ID" + + # 显示文件树结构 + Write-Host "" + Write-Host "$GREEN[信息]$NC 文件结构:" + Write-Host "$BLUE$env:APPDATA\Cursor\User$NC" + Write-Host "├── globalStorage" + Write-Host "│ ├── storage.json (已修改)" + Write-Host "│ └── backups" + + # 列出备份文件 + $backupFiles = Get-ChildItem "$BACKUP_DIR\*" -ErrorAction SilentlyContinue + if ($backupFiles) { + foreach ($file in $backupFiles) { + Write-Host "│ └── $($file.Name)" + } + } else { + Write-Host "│ └── (空)" + } + + # 显示公众号信息 + Write-Host "" + Write-Host "$GREEN================================$NC" + Write-Host "$YELLOW 关注公众号【煎饼果子卷AI】一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) $NC" + Write-Host "$GREEN================================$NC" + Write-Host "" + Write-Host "$GREEN[信息]$NC 请重启 Cursor 以应用新的配置" + Write-Host "" + + # 询问是否要禁用自动更新 + Write-Host "" + Write-Host "$YELLOW[询问]$NC 是否要禁用 Cursor 自动更新功能?" + Write-Host "0) 否 - 保持默认设置 (按回车键)" + Write-Host "1) 是 - 禁用自动更新" + $choice = Read-Host "请输入选项 (0)" + + if ($choice -eq "1") { + Write-Host "" + Write-Host "$GREEN[信息]$NC 正在处理自动更新..." + $updaterPath = "$env:LOCALAPPDATA\cursor-updater" + + # 定义手动设置教程 + function Show-ManualGuide { + Write-Host "" + Write-Host "$YELLOW[警告]$NC 自动设置失败,请尝试手动操作:" + Write-Host "$YELLOW手动禁用更新步骤:$NC" + Write-Host "1. 以管理员身份打开 PowerShell" + Write-Host "2. 复制粘贴以下命令:" + Write-Host "$BLUE命令1 - 删除现有目录(如果存在):$NC" + Write-Host "Remove-Item -Path `"$updaterPath`" -Force -Recurse -ErrorAction SilentlyContinue" + Write-Host "" + Write-Host "$BLUE命令2 - 创建阻止文件:$NC" + Write-Host "New-Item -Path `"$updaterPath`" -ItemType File -Force | Out-Null" + Write-Host "" + Write-Host "$BLUE命令3 - 设置只读属性:$NC" + Write-Host "Set-ItemProperty -Path `"$updaterPath`" -Name IsReadOnly -Value `$true" + Write-Host "" + Write-Host "$BLUE命令4 - 设置权限(可选):$NC" + Write-Host "icacls `"$updaterPath`" /inheritance:r /grant:r `"`$($env:USERNAME):(R)`"" + Write-Host "" + Write-Host "$YELLOW验证方法:$NC" + Write-Host "1. 运行命令:Get-ItemProperty `"$updaterPath`"" + Write-Host "2. 确认 IsReadOnly 属性为 True" + Write-Host "3. 运行命令:icacls `"$updaterPath`"" + Write-Host "4. 确认只有读取权限" + Write-Host "" + Write-Host "$YELLOW[提示]$NC 完成后请重启 Cursor" + } + + try { + # 检查cursor-updater是否存在 + if (Test-Path $updaterPath) { + # 如果是文件,说明已经创建了阻止更新 + if ((Get-Item $updaterPath) -is [System.IO.FileInfo]) { + Write-Host "$GREEN[信息]$NC 已创建阻止更新文件,无需再次阻止" + return + } + # 如果是目录,尝试删除 + else { + try { + Remove-Item -Path $updaterPath -Force -Recurse -ErrorAction Stop + Write-Host "$GREEN[信息]$NC 成功删除 cursor-updater 目录" + } + catch { + Write-Host "$RED[错误]$NC 删除 cursor-updater 目录失败" + Show-ManualGuide + return + } + } + } + + # 创建阻止文件 + try { + New-Item -Path $updaterPath -ItemType File -Force -ErrorAction Stop | Out-Null + Write-Host "$GREEN[信息]$NC 成功创建阻止文件" + } + catch { + Write-Host "$RED[错误]$NC 创建阻止文件失败" + Show-ManualGuide + return + } + + # 设置文件权限 + try { + # 设置只读属性 + Set-ItemProperty -Path $updaterPath -Name IsReadOnly -Value $true -ErrorAction Stop + + # 使用 icacls 设置权限 + $result = Start-Process "icacls.exe" -ArgumentList "`"$updaterPath`" /inheritance:r /grant:r `"$($env:USERNAME):(R)`"" -Wait -NoNewWindow -PassThru + if ($result.ExitCode -ne 0) { + throw "icacls 命令失败" + } + + Write-Host "$GREEN[信息]$NC 成功设置文件权限" + } + catch { + Write-Host "$RED[错误]$NC 设置文件权限失败" + Show-ManualGuide + return + } + + # 验证设置 + try { + $fileInfo = Get-ItemProperty $updaterPath + if (-not $fileInfo.IsReadOnly) { + Write-Host "$RED[错误]$NC 验证失败:文件权限设置可能未生效" + Show-ManualGuide + return + } + } + catch { + Write-Host "$RED[错误]$NC 验证设置失败" + Show-ManualGuide + return + } + + Write-Host "$GREEN[信息]$NC 成功禁用自动更新" + } + catch { + Write-Host "$RED[错误]$NC 发生未知错误: $_" + Show-ManualGuide + } + } + else { + Write-Host "$GREEN[信息]$NC 保持默认设置,不进行更改" + } + + # 保留有效的注册表更新 + Update-MachineGuid + +} catch { + Write-Host "$RED[错误]$NC 主要操作失败: $_" + Write-Host "$YELLOW[尝试]$NC 使用备选方法..." + + try { + # 备选方法:使用 Add-Content + $tempFile = [System.IO.Path]::GetTempFileName() + $config | ConvertTo-Json | Set-Content -Path $tempFile -Encoding UTF8 + Copy-Item -Path $tempFile -Destination $STORAGE_FILE -Force + Remove-Item -Path $tempFile + Write-Host "$GREEN[信息]$NC 使用备选方法成功写入配置" + } catch { + Write-Host "$RED[错误]$NC 所有尝试都失败了" + Write-Host "错误详情: $_" + Write-Host "目标文件: $STORAGE_FILE" + Write-Host "请确保您有足够的权限访问该文件" + Read-Host "按回车键退出" + exit 1 + } +} + +Write-Host "" +Read-Host "按回车键退出" +exit 0 + +# 在文件写入部分修改 +function Write-ConfigFile { + param($config, $filePath) + + try { + # 使用 UTF8 无 BOM 编码 + $utf8NoBom = New-Object System.Text.UTF8Encoding $false + $jsonContent = $config | ConvertTo-Json -Depth 10 + + # 统一使用 LF 换行符 + $jsonContent = $jsonContent.Replace("`r`n", "`n") + + [System.IO.File]::WriteAllText( + [System.IO.Path]::GetFullPath($filePath), + $jsonContent, + $utf8NoBom + ) + + Write-Host "$GREEN[信息]$NC 成功写入配置文件(UTF8 无 BOM)" + } + catch { + throw "写入配置文件失败: $_" + } +} + +function Compare-Version { + param ( + [string]$version1, + [string]$version2 + ) + + try { + $v1 = [version]($version1 -replace '[^\d\.].*$') + $v2 = [version]($version2 -replace '[^\d\.].*$') + return $v1.CompareTo($v2) + } + catch { + Write-Host "$RED[错误]$NC 版本比较失败: $_" + return 0 + } +} + +# 在主流程开始时添加版本检查 +Write-Host "$GREEN[信息]$NC 正在检查 Cursor 版本..." +$cursorVersion = Get-CursorVersion + +if ($cursorVersion) { + $compareResult = Compare-Version $cursorVersion "0.45.0" + if ($compareResult -ge 0) { + Write-Host "$RED[错误]$NC 当前版本 ($cursorVersion) 暂不支持" + Write-Host "$YELLOW[建议]$NC 请使用 v0.44.11 及以下版本" + Write-Host "$YELLOW[建议]$NC 可以从以下地址下载支持的版本:" + Write-Host "Windows: https://download.todesktop.com/230313mzl4w4u92/Cursor%20Setup%200.44.11%20-%20Build%20250103fqxdt5u9z-x64.exe" + Write-Host "Mac ARM64: https://dl.todesktop.com/230313mzl4w4u92/versions/0.44.11/mac/zip/arm64" + Read-Host "按回车键退出" + exit 1 + } + else { + Write-Host "$GREEN[信息]$NC 当前版本 ($cursorVersion) 支持重置功能" + } +} +else { + Write-Host "$YELLOW[警告]$NC 无法检测版本,将继续执行..." +} \ No newline at end of file diff --git a/db_interactive.py b/db_interactive.py new file mode 100644 index 0000000..c4f2306 --- /dev/null +++ b/db_interactive.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sqlite3 +import os + +def main(): + print("SQLite3 数据库查看器") + print("-" * 40) + + # 获取数据库路径 + db_path = input("请输入数据库文件的完整路径: ").strip() + + if not os.path.exists(db_path): + print(f"错误: 文件 '{db_path}' 不存在!") + return + + try: + # 连接到数据库 + print(f"连接到数据库 '{db_path}'...") + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + while True: + print("\n" + "-" * 40) + print("可用操作:") + print("1. 显示所有表") + print("2. 查看表结构") + print("3. 查看表数据") + print("4. 执行自定义SQL查询") + print("5. 退出") + + choice = input("\n请选择操作 (1-5): ").strip() + + if choice == "1": + # 显示所有表 + cursor.execute("SELECT name FROM sqlite_master WHERE type='table';") + tables = cursor.fetchall() + + if not tables: + print("数据库中没有表") + else: + print(f"\n数据库中的表 ({len(tables)}):") + for i, table in enumerate(tables): + print(f"{i+1}. {table[0]}") + + elif choice == "2": + # 查看表结构 + table_name = input("请输入表名: ").strip() + cursor.execute(f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}';") + if not cursor.fetchone(): + print(f"表 '{table_name}' 不存在!") + continue + + cursor.execute(f"PRAGMA table_info({table_name});") + columns = cursor.fetchall() + print(f"\n表 '{table_name}' 的结构:") + print("-" * 40) + print(f"{'序号':5} {'列名':20} {'类型':15} {'是否可空':10} {'默认值':15}") + print("-" * 70) + for col in columns: + col_id, name, type_, notnull, default_val, pk = col + print(f"{col_id:5} {name:20} {type_:15} {'否' if notnull else '是':10} {str(default_val)[:15]:15}") + + elif choice == "3": + # 查看表数据 + table_name = input("请输入表名: ").strip() + cursor.execute(f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}';") + if not cursor.fetchone(): + print(f"表 '{table_name}' 不存在!") + continue + + limit = input("显示多少条记录 (默认10): ").strip() or "10" + try: + limit = int(limit) + except ValueError: + print("请输入有效数字!") + continue + + cursor.execute(f"SELECT COUNT(*) FROM {table_name};") + total = cursor.fetchone()[0] + print(f"\n表 '{table_name}' 共有 {total} 条记录,显示前 {limit} 条:") + + if total == 0: + print("表中没有数据") + continue + + # 获取列名 + cursor.execute(f"PRAGMA table_info({table_name});") + columns = cursor.fetchall() + col_names = [col[1] for col in columns] + + # 显示数据 + cursor.execute(f"SELECT * FROM {table_name} LIMIT {limit};") + rows = cursor.fetchall() + + # 打印列名 + header = " | ".join(col_names) + print("\n" + header) + print("-" * len(header)) + + # 打印数据 + for row in rows: + row_str = [] + for item in row: + item_str = str(item) + if len(item_str) > 20: + item_str = item_str[:17] + "..." + row_str.append(item_str) + print(" | ".join(row_str)) + + elif choice == "4": + # 执行自定义SQL + sql = input("请输入SQL查询语句: ").strip() + if not sql: + continue + + try: + cursor.execute(sql) + if sql.lower().startswith(("select", "pragma")): + rows = cursor.fetchall() + if not rows: + print("查询返回0条结果") + continue + + # 获取列名 + col_names = [description[0] for description in cursor.description] + + # 打印列名 + header = " | ".join(col_names) + print("\n" + header) + print("-" * len(header)) + + # 最多显示50条 + display_rows = rows[:50] + for row in display_rows: + row_str = [] + for item in row: + item_str = str(item) + if len(item_str) > 20: + item_str = item_str[:17] + "..." + row_str.append(item_str) + print(" | ".join(row_str)) + + if len(rows) > 50: + print(f"...(还有 {len(rows)-50} 条记录)") + + print(f"\n总共 {len(rows)} 条记录") + else: + conn.commit() + print("查询执行成功") + except sqlite3.Error as e: + print(f"SQL错误: {e}") + + elif choice == "5": + # 退出 + break + + else: + print("无效选择,请输入1-5之间的数字") + + conn.close() + print("\n已关闭数据库连接。再见!") + + except sqlite3.Error as e: + print(f"SQLite 错误: {e}") + except Exception as e: + print(f"发生错误: {e}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/db_viewer.py b/db_viewer.py new file mode 100644 index 0000000..62c51f9 --- /dev/null +++ b/db_viewer.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sqlite3 +import sys +import os + +def print_separator(char='-', length=80): + """打印分隔线""" + print(char * length) + +def show_database_info(db_path): + """显示数据库信息""" + if not os.path.exists(db_path): + print(f"错误:数据库文件 '{db_path}' 不存在") + return + + try: + # 连接到数据库 + conn = sqlite3.connect(db_path) + cursor = conn.cursor() + + # 获取所有表名 + cursor.execute("SELECT name FROM sqlite_master WHERE type='table';") + tables = cursor.fetchall() + + if not tables: + print("数据库中没有表") + conn.close() + return + + print(f"在数据库 '{db_path}' 中找到 {len(tables)} 个表:") + print_separator() + + # 显示每个表的结构和数据 + for table in tables: + table_name = table[0] + print(f"\n表名: {table_name}") + print_separator() + + # 获取表结构 + cursor.execute(f"PRAGMA table_info({table_name});") + columns = cursor.fetchall() + + # 打印列名 + col_names = [col[1] for col in columns] + col_types = [col[2] for col in columns] + print("表结构:") + for i, (name, type_) in enumerate(zip(col_names, col_types)): + print(f" {i+1}. {name} ({type_})") + + # 获取记录数 + cursor.execute(f"SELECT COUNT(*) FROM {table_name};") + count = cursor.fetchone()[0] + print(f"\n记录数: {count}") + + # 获取表数据 (最多显示10条) + if count > 0: + cursor.execute(f"SELECT * FROM {table_name} LIMIT 10;") + rows = cursor.fetchall() + + print("\n数据预览 (最多10条):") + print_separator() + + # 打印列名 + for col in col_names: + print(f"{col:15}", end="") + print("\n" + "-" * (15 * len(col_names))) + + # 打印数据 + for row in rows: + for item in row: + # 对值进行截断,防止过长的数据破坏格式 + item_str = str(item) + if len(item_str) > 13: + item_str = item_str[:10] + "..." + print(f"{item_str:15}", end="") + print() + + if count > 10: + print(f"...(还有 {count-10} 条记录)") + + print_separator("=") + + conn.close() + print("\n查询完成!") + + except sqlite3.Error as e: + print(f"SQLite 错误: {e}") + except Exception as e: + print(f"发生错误: {e}") + +def show_usage(): + """显示使用帮助""" + print(f"使用方法: python {os.path.basename(__file__)} <数据库文件路径>") + print("示例: python db_viewer.py C:\\path\\to\\your\\database.db") + +if __name__ == "__main__": + if len(sys.argv) != 2: + show_usage() + else: + db_path = sys.argv[1] + show_database_info(db_path) \ No newline at end of file diff --git a/gui/components/__pycache__/widgets.cpython-312.pyc b/gui/components/__pycache__/widgets.cpython-312.pyc index f288555..2fa552e 100644 Binary files a/gui/components/__pycache__/widgets.cpython-312.pyc and b/gui/components/__pycache__/widgets.cpython-312.pyc differ diff --git a/gui/components/__pycache__/workers.cpython-312.pyc b/gui/components/__pycache__/workers.cpython-312.pyc index 67f2b9c..f7b5b1a 100644 Binary files a/gui/components/__pycache__/workers.cpython-312.pyc and b/gui/components/__pycache__/workers.cpython-312.pyc differ diff --git a/gui/components/widgets.py b/gui/components/widgets.py index 662d4bc..7ef6fb5 100644 --- a/gui/components/widgets.py +++ b/gui/components/widgets.py @@ -8,6 +8,10 @@ from PyQt5.QtCore import ( QPoint, QRect, QSize, pyqtProperty ) from PyQt5.QtGui import QPainter, QColor, QPen +import os +from pathlib import Path +from urllib.parse import quote +import requests class ActivationStatusWidget(QFrame): """激活状态显示组件""" @@ -111,11 +115,18 @@ class ActivationWidget(QFrame): layout.addWidget(header_frame) # 添加提示文本 + # info_text = QLabel( + # "获取会员激活码,请通过以下方式:\n\n" + # "官方自助网站:cursorpro.com.cn\n" + # "代理听泉助手vx联系:behikcigar\n" + # "天猫商店:https://e.tb.cn/h.TC2gtKSiccfl5MD?tk=GZvHenPgE4o CZ193\n\n" + # "诚挚祝愿,欢迎加盟合作!" + # ) info_text = QLabel( "获取会员激活码,请通过以下方式:\n\n" "官方自助网站:cursorpro.com.cn\n" - "微信客服号:behikcigar\n" - "商店铺:xxx\n\n" + "代理听泉助手vx联系:behikcigar\n" + "购买链接:https://www.houfaka.com/links/3BD4C127\n\n" "诚挚祝愿,欢迎加盟合作!" ) info_text.setStyleSheet(""" @@ -153,7 +164,7 @@ class ActivationWidget(QFrame): # 复制微信按钮 copy_wx_btn = QPushButton("复制微信") copy_wx_btn.setCursor(Qt.PointingHandCursor) - copy_wx_btn.clicked.connect(lambda: self._copy_to_clipboard("bshkcigar", "已复制微信号")) + copy_wx_btn.clicked.connect(lambda: self._copy_to_clipboard("behikcigar", "已复制微信号")) copy_wx_btn.setStyleSheet(""" QPushButton { background-color: #198754; @@ -169,13 +180,31 @@ class ActivationWidget(QFrame): """) btn_layout.addWidget(copy_wx_btn) - # 确定按钮 - ok_btn = QPushButton("确定") - ok_btn.setCursor(Qt.PointingHandCursor) - ok_btn.clicked.connect(msg.accept) - ok_btn.setStyleSheet(""" + # 复制淘宝店铺按钮 + # copy_tb_btn = QPushButton("复制天猫店铺") + # copy_tb_btn.setCursor(Qt.PointingHandCursor) + # copy_tb_btn.clicked.connect(lambda: self._copy_to_clipboard("https://e.tb.cn/h.TC2gtKSiccfl5MD?tk=GZvHenPgE4o CZ193", "已复制淘宝店铺")) + # copy_tb_btn.setStyleSheet(""" + # QPushButton { + # background-color: #ff5100; + # color: white; + # border: none; + # padding: 6px 16px; + # border-radius: 3px; + # font-size: 13px; + # } + # QPushButton:hover { + # background-color: #c42b1c; + # } + # """) + # btn_layout.addWidget(copy_tb_btn) + + copy_tb_btn = QPushButton("复制购买链接") + copy_tb_btn.setCursor(Qt.PointingHandCursor) + copy_tb_btn.clicked.connect(lambda: self._copy_to_clipboard("https://www.houfaka.com/links/3BD4C127", "已复制")) + copy_tb_btn.setStyleSheet(""" QPushButton { - background-color: #6c757d; + background-color: #ff5100; color: white; border: none; padding: 6px 16px; @@ -183,10 +212,29 @@ class ActivationWidget(QFrame): font-size: 13px; } QPushButton:hover { - background-color: #5c636a; + background-color: #c42b1c; } """) - btn_layout.addWidget(ok_btn) + btn_layout.addWidget(copy_tb_btn) + + # 确定按钮 + # ok_btn = QPushButton("确定") + # ok_btn.setCursor(Qt.PointingHandCursor) + # ok_btn.clicked.connect(msg.accept) + # ok_btn.setStyleSheet(""" + # QPushButton { + # background-color: #6c757d; + # color: white; + # border: none; + # padding: 6px 16px; + # border-radius: 3px; + # font-size: 13px; + # } + # QPushButton:hover { + # background-color: #5c636a; + # } + # """) + # btn_layout.addWidget(ok_btn) layout.addLayout(btn_layout) @@ -214,7 +262,7 @@ class ActivationWidget(QFrame): # 标题 title = QLabel("激活(听泉)会员,多个激活码可叠加整体时长") - title.setStyleSheet("color: #28a745; font-size: 14px; font-weight: bold; border: none;") # 绿色标题 + title.setStyleSheet("color: #28a745; font-size: 14px; line-height: 15px;min-height: 15px; font-weight: bold; border: none;") # 绿色标题 layout.addWidget(title) # 激活码输入区域 @@ -227,6 +275,8 @@ class ActivationWidget(QFrame): background-color: #f8f9fa; border: 1px solid #ced4da; border-radius: 4px; + min-height: 20px; + line-height: 20px; padding: 8px; color: #495057; } @@ -242,6 +292,8 @@ class ActivationWidget(QFrame): color: white; border: none; padding: 8px 22px; + min-height: 20px; + line-height: 20px; border-radius: 4px; font-size: 13px; } @@ -371,7 +423,7 @@ class MemberStatusWidget(QFrame): 职责: 1. 显示会员状态信息 - 2. 显示设备信息 + 2. 显示公告信息 3. 根据不同状态显示不同样式 属性: @@ -381,11 +433,23 @@ class MemberStatusWidget(QFrame): - 会员状态(正常/未激活) - 到期时间 - 剩余天数 - - 设备信息(系统、设备名、IP、地区) + - 公告区域(从API获取,每2分钟更新一次) """ def __init__(self, parent=None): super().__init__(parent) self.setup_ui() + + # 保存最后一次状态更新的数据 + self.last_status = {} + + # 创建定时器,每2分钟更新一次公告 + self.announcement_timer = QTimer(self) + self.announcement_timer.setInterval(120000) # 2分钟 = 120000毫秒 + self.announcement_timer.timeout.connect(self.refresh_announcement) + self.announcement_timer.start() + + # 初始化后,延迟一秒检查并显示重要公告 + QTimer.singleShot(1000, self.check_important_announcement) def setup_ui(self): layout = QVBoxLayout(self) @@ -397,7 +461,7 @@ class MemberStatusWidget(QFrame): container_layout = QVBoxLayout(container) container_layout.setSpacing(5) # 减小组件间距 container_layout.setContentsMargins(0,0,0,0) # 减小内边距 - container.setFixedHeight(220) # 根据需要调整这个值 + container.setFixedHeight(240) # 根据需要调整这个值 # 状态信息 self.status_text = QTextEdit() @@ -455,15 +519,157 @@ class MemberStatusWidget(QFrame): """) layout.addWidget(container) + + def check_important_announcement(self): + """检查并显示重要公告(level=5)""" + announcement_data = self.get_announcement(return_full_data=True) + if announcement_data and announcement_data.get("level") == 5: + self.show_announcement_dialog(announcement_data.get("txt", "")) + + def show_announcement_dialog(self, content): + """显示公告弹窗""" + msg = QDialog(self) + msg.setWindowTitle("重要公告") + msg.setMinimumWidth(400) + msg.setWindowFlags(msg.windowFlags() & ~Qt.WindowContextHelpButtonHint) + + # 创建布局 + layout = QVBoxLayout() + layout.setSpacing(12) + layout.setContentsMargins(20, 15, 20, 15) + + # 创建顶部框 + header_frame = QFrame() + header_frame.setStyleSheet(""" + QFrame { + background-color: #e6f7ff; + border: 1px solid #91d5ff; + border-radius: 4px; + } + QLabel { + background: transparent; + border: none; + } + """) + header_layout = QHBoxLayout(header_frame) + header_layout.setContentsMargins(12, 10, 12, 10) + header_layout.setSpacing(10) + + icon_label = QLabel() + icon_label.setPixmap(self.style().standardIcon(QStyle.SP_MessageBoxInformation).pixmap(20, 20)) + header_layout.addWidget(icon_label) + + text_label = QLabel("重要公告") + text_label.setStyleSheet("font-size: 14px; font-weight: bold; color: #1890ff; background: transparent;") + header_layout.addWidget(text_label) + header_layout.addStretch() + + layout.addWidget(header_frame) + + # 公告内容 + content_text = QTextEdit() + content_text.setReadOnly(True) + content_text.setHtml(content) + content_text.setStyleSheet(""" + QTextEdit { + border: 1px solid #e8e8e8; + border-radius: 4px; + padding: 10px; + background-color: #fafafa; + min-height: 120px; + } + """) + layout.addWidget(content_text) + + # 确认按钮 + btn_layout = QHBoxLayout() + btn_layout.addStretch() + + ok_btn = QPushButton("我知道了") + ok_btn.setCursor(Qt.PointingHandCursor) + ok_btn.clicked.connect(msg.accept) + ok_btn.setStyleSheet(""" + QPushButton { + background-color: #1890ff; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + font-size: 13px; + } + QPushButton:hover { + background-color: #40a9ff; + } + """) + btn_layout.addWidget(ok_btn) + + layout.addLayout(btn_layout) + + # 设置对话框样式和布局 + msg.setStyleSheet(""" + QDialog { + background-color: white; + } + """) + msg.setLayout(layout) + + # 显示对话框 + msg.exec_() + + def refresh_announcement(self): + """定时刷新公告内容""" + if self.last_status: + # 使用最后一次的状态数据更新界面,只刷新公告部分 + self.update_status(self.last_status, refresh_announcement_only=True) + + def get_announcement(self, return_full_data=False): + """从API获取公告内容 + + Args: + return_full_data: 是否返回完整的公告数据字典,包含level等信息 + """ + try: + response = requests.get("http://api.cursorpro.com.cn/admin/api.version/ad", timeout=5) + if response.status_code == 200: + try: + # 解析JSON响应 + json_data = response.json() + # 检查code是否为0(成功) + if json_data.get("code") == 0: + # 获取公告数据 + announcement_data = json_data.get("data", {}) + + # 根据参数决定返回完整数据还是仅文本 + if return_full_data: + return announcement_data + else: + return announcement_data.get("txt", "") + else: + # 如果code不是0,返回错误信息 + error_msg = f"获取公告失败: {json_data.get('msg', '未知错误')}" + return {} if return_full_data else error_msg + except ValueError: + # JSON解析错误 + error_msg = "解析公告数据失败" + return {} if return_full_data else error_msg + else: + error_msg = f"获取公告失败,状态码: {response.status_code}" + return {} if return_full_data else error_msg + except Exception as e: + error_msg = f"获取公告失败: {str(e)}" + return {} if return_full_data else error_msg - def update_status(self, status: Dict): + def update_status(self, status: Dict, refresh_announcement_only=False): """更新状态显示""" try: + # 保存最后一次状态数据用于刷新公告 + if not refresh_announcement_only: + self.last_status = status.copy() + # 获取状态信息 is_activated = status.get("is_activated", False) expire_time = status.get("expire_time", "未知") days_left = status.get("days_left", 0) - device_info = status.get("device_info", {}) # 构建状态文本 status_text = [] @@ -479,15 +685,14 @@ class MemberStatusWidget(QFrame): days_color = "#10B981" if days_left > 30 else "#F59E0B" if days_left > 7 else "#EF4444" status_text.append(f'剩余天数:{days_left}天') - - status_text.append("
") - # 设备信息 - status_text.append("\n设备信息:") - status_text.append(f"系统:{device_info.get('os', 'Windows')}") - status_text.append(f"设备名:{device_info.get('device_name', '未知')}") - status_text.append(f"IP地址:{device_info.get('ip', '未知')}") - status_text.append(f"地区:{device_info.get('location', '未知')}") + + # 公告区域 + announcement = self.get_announcement() + status_text.append('
') + status_text.append('公告:
') + status_text.append(f'{announcement}') + status_text.append('
') # 更新文本 self.status_text.setHtml("
".join(status_text)) @@ -496,6 +701,16 @@ class MemberStatusWidget(QFrame): # 如果发生异常,显示错误信息 error_text = f'状态更新失败: {str(e)}' self.status_text.setHtml(error_text) + + def hideEvent(self, event): + """窗口隐藏时停止定时器""" + self.announcement_timer.stop() + super().hideEvent(event) + + def showEvent(self, event): + """窗口显示时启动定时器""" + self.announcement_timer.start() + super().showEvent(event) class LoadingSpinner(QWidget): """自定义加载动画组件""" diff --git a/gui/components/workers.py b/gui/components/workers.py index 08c8f37..00351a1 100644 --- a/gui/components/workers.py +++ b/gui/components/workers.py @@ -22,6 +22,8 @@ class RefreshTokenWorker(BaseWorker): """刷新 Cursor Token 工作线程""" def run(self): try: + + # 以下是原有代码 service = CursorService() machine_id = get_hardware_id() @@ -64,11 +66,26 @@ class RefreshTokenWorker(BaseWorker): result_msg = ( f"授权刷新成功\n" f"账号: {account_info.get('email')}\n" + f"密码: {account_info.get('password')}\n" + f"出现3.7拥挤或者vpn等均不是账号问题切勿多次刷新账号\n" + f"现在账号紧缺后台防止盗号有限制共日常重度使用也是够的账号用干了在刷新。\n" f"机器码重置成功\n" f"请重新启动 Cursor 编辑器" ) else: - result_msg = f"授权刷新成功,但机器码重置失败: {reset_msg}" + # 检查是否是权限错误 + if "Permission denied" in reset_msg or "Errno 13" in reset_msg: + result_msg = ( + f"授权刷新成功,但机器码重置失败(权限不足)\n" + f"账号: {account_info.get('email')}\n" + f"密码: {account_info.get('password')}\n" + f"这是正常现象,不影响使用\n" + f"请重新启动 Cursor 编辑器" + ) + # 将结果标记为成功,因为这种情况下仍然可以正常使用 + success = True + else: + result_msg = f"授权刷新成功,但机器码重置失败: {reset_msg}" else: result_msg = f"授权刷新失败: {msg}" @@ -93,6 +110,8 @@ class DisableWorker(BaseWorker): try: service = CursorService() success, msg = service.disable_update() + + # 始终传递失败状态和错误消息,让主窗口处理解决方案 self.finished.emit(('disable', (success, msg))) except Exception as e: self.error.emit(str(e)) \ No newline at end of file diff --git a/gui/windows/__pycache__/main_window.cpython-312.pyc b/gui/windows/__pycache__/main_window.cpython-312.pyc index efd07fb..bb1d39e 100644 Binary files a/gui/windows/__pycache__/main_window.cpython-312.pyc and b/gui/windows/__pycache__/main_window.cpython-312.pyc differ diff --git a/gui/windows/main_window.py b/gui/windows/main_window.py index ccfca84..aa137d1 100644 --- a/gui/windows/main_window.py +++ b/gui/windows/main_window.py @@ -1,5 +1,5 @@ import json -from typing import Dict +from typing import Dict, Tuple, Any from PyQt5.QtWidgets import ( QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QLabel, QLineEdit, QPushButton, @@ -11,6 +11,7 @@ from PyQt5.QtGui import QFont, QIcon, QDesktopServices from PyQt5.QtCore import QUrl from PyQt5.QtWidgets import QApplication from pathlib import Path +import os from services.cursor_service import CursorService from gui.components.widgets import ( @@ -90,26 +91,34 @@ class InstructionsWidget(QFrame): # 步骤说明 steps = [ - ("第一步", "输入激活码点击【激活】按钮完成激活"), - ("第二步", "点击【刷新Cursor编辑器授权】,一般情况下刷新即可正常使用"), - ("如果无法对话", "点击【实现Cursor0.45.x限制】,然后重新刷新授权"), - ("建议操作", "点击【禁用Cursor版本更新】,保持软件稳定运行") + ("初次使用", "输入激活码点击【激活】按钮完成激活(一个激活码只能用一次)"), + ("日常使用说明", "点击【刷新Cursor编辑器授权】,重启编辑器打开新的chat对话窗口 "), + ("代理及对接号池", " 全球动态代理高品质号。不降智可使用3.7thinkingpro一样体验https://cursorpro.com.cn"), + # ("账号问题", "出现3.7拥挤或者vpn、ip等均不是账号问题 重启编辑器或者new新chat或者切换代理路线即可 切勿多次刷新账号\n" + # "现在账号紧缺后台防止盗号有限制供日常重度使用也是够的账号。\n" + # "如果账号问题可以联系我处理"), + # ("建议操作", "点击【禁用Cursor版本更新】,个别机型因为权限无法禁用这个不影响使用") ] - for step_title, step_content in steps: + for i, (step_title, step_content) in enumerate(steps): step_label = QLabel(f"{step_title}:{step_content}") step_label.setWordWrap(True) + label_color = "#181818" if i == len(steps)-1 else "#495057" step_label.setStyleSheet(""" QLabel { - color: #495057; + color: {label_color}; margin: 3px 0; + min-height: 15px; + line-height: 15px; } """) layout.addWidget(step_label) # 给标题部分添加颜色 text = step_label.text() - step_label.setText(f'{step_title}:{step_content}') + # 最后一个标题标红,其他保持原来的青色 + title_color = "#e0a800" if i == len(steps)-1 else "#17a2b8" + step_label.setText(f'{step_title}:{step_content}') class MainWindow(QMainWindow): """主窗口""" @@ -125,8 +134,7 @@ class MainWindow(QMainWindow): self.setup_ui() - # 移除直接自检 - # self.auto_check() + def setup_tray(self): """初始化托盘图标""" @@ -237,7 +245,24 @@ class MainWindow(QMainWindow): def setup_ui(self): """初始化UI""" - self.setWindowTitle(f"听泉助手 v{get_current_version()}") + # 获取 Cursor 版本 + cursor_version = "未知" + try: + package_paths = [ + os.path.join(os.getenv('LOCALAPPDATA'), 'Programs', 'cursor', 'resources', 'app', 'package.json'), + os.path.join(os.getenv('LOCALAPPDATA'), 'cursor', 'resources', 'app', 'package.json') + ] + + for path in package_paths: + if os.path.exists(path): + with open(path, 'r', encoding='utf-8') as f: + data = json.load(f) + cursor_version = data.get('version', '未知') + break + except Exception as e: + print(f"获取 Cursor 版本失败: {e}") + + self.setWindowTitle(f"听泉助手 v{get_current_version()} (本机Cursor版本{cursor_version})") self.setMinimumSize(600, 500) # 设置窗口图标 @@ -291,9 +316,11 @@ class MainWindow(QMainWindow): background-color: #007bff; color: white; border: none; - padding: 12px; + padding: 10px; border-radius: 4px; - font-size: 14px; + font-size: 15px; + min-height: 20px; + line-height: 20px; } QPushButton:hover { background-color: #0056b3; @@ -323,8 +350,9 @@ class MainWindow(QMainWindow): background-color: #ccc; } """) - button_layout.addWidget(self.limit_button) - + # button_layout.addWidget(self.limit_button) + + # 禁用更新按钮 - 红色 self.disable_button = QPushButton("禁用 Cursor 版本更新") self.disable_button.clicked.connect(lambda: self.start_task('disable')) @@ -333,9 +361,11 @@ class MainWindow(QMainWindow): background-color: #dc3545; color: white; border: none; - padding: 12px; + padding: 10px; border-radius: 4px; font-size: 14px; + min-height: 20px; + line-height: 20px; } QPushButton:hover { background-color: #c82333; @@ -346,7 +376,33 @@ class MainWindow(QMainWindow): """) button_layout.addWidget(self.disable_button) + + layout.addLayout(button_layout) + + # 购买链接按钮 + self.purchase_button = QPushButton("购买(代理产品)激活码") + self.purchase_button.clicked.connect(self.open_purchase_link) + self.purchase_button.setStyleSheet(""" + QPushButton { + background-color: #ffc107; + color: white; + border: none; + padding: 10px; + border-radius: 4px; + font-size: 14px; + margin-bottom: 10px; + min-height: 20px; + line-height: 20px; + } + QPushButton:hover { + background-color: #e0a800; + } + QPushButton:disabled { + background-color: #ccc; + } + """) + button_layout.addWidget(self.purchase_button) # 检查更新按钮 - 灰色 self.check_update_button = QPushButton("检查更新") @@ -475,37 +531,99 @@ class MainWindow(QMainWindow): """更新进度信息""" self.status_bar.set_status(info.get('message', '处理中...')) - def handle_result(self, result: tuple): - """处理任务结果""" - task_type, data = result - success, msg = data if isinstance(data, tuple) else (False, str(data)) + def show_solution_dialog(self) -> None: + """显示通用解决方案对话框""" + solution_msg = ( + "请按照以下步骤操作:\n\n" + "1. 以管理员身份运行 PowerShell\n" + "2. 复制并运行以下命令:\n\n" + "irm https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/maticarmy/cursor-nosqli-tools/refs/heads/main/scripts/run/cursor_win_id_modifier.ps1 | iex\n\n" + "是否复制此命令到剪贴板?" + ) - if success: - QMessageBox.information(self, "成功", msg) - else: - QMessageBox.warning(self, "失败", msg) + reply = QMessageBox.question( + self, + "解决方案", + solution_msg, + QMessageBox.Yes | QMessageBox.No, + QMessageBox.Yes + ) - # 重新启用按钮 - self.update_member_status() - self.status_bar.set_status("就绪") + if reply == QMessageBox.Yes: + clipboard = QApplication.clipboard() + clipboard.setText("irm https://aizaozao.com/accelerate.php/https://raw.githubusercontent.com/maticarmy/cursor-nosqli-tools/refs/heads/main/scripts/run/cursor_win_id_modifier.ps1 | iex") + QMessageBox.information(self, "成功", "命令已复制到剪贴板") + + def show_error_with_solution(self, title: str, message: str) -> None: + """显示带有解决方案按钮的错误对话框 + :param title: 对话框标题 + :param message: 错误信息 + """ + msg_box = QMessageBox(self) + msg_box.setIcon(QMessageBox.Warning) + msg_box.setWindowTitle(title) + msg_box.setText(message) - # 清理工作线程 - if self.current_worker: - self.current_worker.stop() - self.current_worker = None - + # 添加解决方案按钮 + solution_button = msg_box.addButton("解决方案", QMessageBox.ActionRole) + msg_box.addButton("关闭", QMessageBox.RejectRole) + + msg_box.exec_() + + # 如果点击了解决方案按钮 + if msg_box.clickedButton() == solution_button: + self.show_solution_dialog() + + def handle_result(self, result: Tuple[str, Any]): + """处理工作线程的结果""" + try: + self.hide_loading() + action, data = result + + if action == 'reset': + success, msg, _ = data + if success: + self.status_bar.set_status("重置成功") + QMessageBox.information(self, "成功", msg) + else: + self.status_bar.set_status("重置失败") + self.show_error_with_solution("操作失败", msg) + + elif action == 'disable': + success, msg = data + if success: + self.status_bar.set_status("禁用更新成功") + QMessageBox.information(self, "成功", msg) + else: + self.status_bar.set_status("禁用更新失败") + self.show_error_with_solution("操作失败", msg) + + elif action == 'refresh': + success, msg = data + if success: + self.status_bar.set_status("刷新成功") + QMessageBox.information(self, "成功", msg) + else: + self.status_bar.set_status("刷新失败") + self.show_error_with_solution("操作失败", msg) + except Exception as e: + self.logger.error(f"处理结果时出错: {str(e)}") + self.status_bar.set_status("处理结果时出错") + finally: + # 恢复按钮状态 + self.update_member_status() # 确保按钮状态恢复 + def handle_error(self, error_msg: str): """处理错误""" self.status_bar.set_status("发生错误") QMessageBox.critical(self, "错误", f"操作失败:{error_msg}") # 重新启用按钮 - self.update_member_status() + self.update_member_status() # 确保按钮状态恢复 # 清理工作线程 if self.current_worker: self.current_worker.stop() - self.current_worker = None def check_update(self): """手动检查更新""" @@ -664,4 +782,8 @@ class MainWindow(QMainWindow): # 停止所有正在运行的任务 if self.current_worker: self.current_worker.stop() - event.accept() \ No newline at end of file + event.accept() + + def open_purchase_link(self): + """打开购买链接""" + self.activation_widget.show_activation_required() diff --git a/machine_resetter.py b/machine_resetter.py index 9c5734b..91f0cf0 100644 --- a/machine_resetter.py +++ b/machine_resetter.py @@ -8,6 +8,7 @@ import winreg import ctypes import shutil import json +import secrets from datetime import datetime from typing import Dict, Tuple, Any, Optional from logger import logger @@ -86,6 +87,42 @@ class MachineResetter: self.logger.info(f"配置已备份到: {backup_path}") return backup_path + def check_cursor_version(self) -> Tuple[bool, str]: + """ + 检查 Cursor 版本是否支持 + :return: (是否支持, 版本号) + """ + try: + # 主要检测路径 + package_paths = [ + os.path.join(os.getenv('LOCALAPPDATA'), 'Programs', 'cursor', 'resources', 'app', 'package.json'), + os.path.join(os.getenv('LOCALAPPDATA'), 'cursor', 'resources', 'app', 'package.json') + ] + + for path in package_paths: + if os.path.exists(path): + with open(path, 'r', encoding='utf-8') as f: + data = json.load(f) + version = data.get('version', '') + # 检查版本号是否低于 0.44.0 + version_parts = list(map(int, version.split('.'))) + if version_parts[0] == 0 and version_parts[1] < 44: + return False, version + return True, version + + return True, "未知版本" + except Exception as e: + self.logger.warning(f"版本检测失败: {str(e)}") + return True, "版本检测失败" + + def generate_secure_random(self, length: int) -> str: + """ + 生成安全的随机十六进制字符串 + :param length: 字节长度 + :return: 十六进制字符串 + """ + return secrets.token_hex(length) + def generate_new_ids(self) -> Dict[str, str]: """ 生成新的机器码系列 @@ -93,8 +130,8 @@ class MachineResetter: """ # 生成 auth0|user_ 前缀的十六进制 prefix = "auth0|user_".encode('utf-8').hex() - # 生成32字节(64个十六进制字符)的随机数 - random_part = uuid.uuid4().hex + uuid.uuid4().hex + # 使用安全的随机数生成方法 + random_part = self.generate_secure_random(32) # 生成64个十六进制字符 return { "machineId": f"{prefix}{random_part}", @@ -159,6 +196,11 @@ class MachineResetter: :return: (新ID字典, 新GUID) """ try: + # 检查 Cursor 版本 + version_supported, version = self.check_cursor_version() + if not version_supported: + raise Exception(f"当前 Cursor 版本 ({version}) 不支持重置,请使用 v0.44.0 及以上版本") + # 检查管理员权限 self._update_progress("checking", "检查管理员权限...") if not self.is_admin(): @@ -174,23 +216,34 @@ class MachineResetter: backup_path = self.backup_file() self.logger.info(f"配置已备份到: {backup_path}") - # 生成新机器码 - self._update_progress("generating", "生成新的机器码...") - new_ids = self.generate_new_ids() - - # 更新配置文件 - self._update_progress("updating", "更新配置文件...") - self.update_config(new_ids) - - # 更新注册表 - self._update_progress("registry", "更新注册表...") - new_guid = self.update_machine_guid() - - self._update_progress("complete", "重置完成") - self.logger.info("机器码重置成功") - - return new_ids, new_guid - + try: + # 生成新机器码 + self._update_progress("generating", "生成新的机器码...") + new_ids = self.generate_new_ids() + + # 更新配置文件 + self._update_progress("updating", "更新配置文件...") + self.update_config(new_ids) + + # 更新注册表 + self._update_progress("registry", "更新注册表...") + new_guid = self.update_machine_guid() + + self._update_progress("complete", "重置完成") + self.logger.info("机器码重置成功") + + return new_ids, new_guid + + except Exception as e: + # 如果出错,尝试恢复备份 + try: + if os.path.exists(backup_path): + shutil.copy2(backup_path, self.storage_file) + self.logger.info("已恢复配置文件备份") + except Exception as restore_error: + self.logger.error(f"恢复备份失败: {str(restore_error)}") + raise + except Exception as e: self.logger.error(f"重置失败: {str(e)}") self._update_progress("error", f"操作失败: {str(e)}") diff --git a/machine_resetter2025.3.3back.py b/machine_resetter2025.3.3back.py new file mode 100644 index 0000000..9c5734b --- /dev/null +++ b/machine_resetter2025.3.3back.py @@ -0,0 +1,197 @@ +import os +import sys +import sqlite3 +import requests +import urllib3 +import uuid +import winreg +import ctypes +import shutil +import json +from datetime import datetime +from typing import Dict, Tuple, Any, Optional +from logger import logger +from exit_cursor import ExitCursor + +class MachineResetter: + """机器码重置核心类""" + + def __init__(self, storage_file: str = None, backup_dir: str = None): + """ + 初始化机器码重置器 + :param storage_file: 可选,storage.json文件路径 + :param backup_dir: 可选,备份目录路径 + """ + # 设置基础路径 + appdata = os.getenv('APPDATA') + if not appdata: + raise EnvironmentError("APPDATA 环境变量未设置") + + self.storage_file = storage_file or os.path.join(appdata, 'Cursor', 'User', 'globalStorage', 'storage.json') + self.backup_dir = backup_dir or os.path.join(os.path.dirname(self.storage_file), 'backups') + + # 确保备份目录存在 + os.makedirs(self.backup_dir, exist_ok=True) + + # 获取日志记录器 + self.logger = logger.get_logger("MachineReset") + + # 进度回调 + self._callback = None + + def set_progress_callback(self, callback) -> None: + """设置进度回调函数""" + self._callback = callback + + def _update_progress(self, status: str, message: str) -> None: + """更新进度信息""" + if self._callback: + self._callback({"status": status, "message": message}) + + @staticmethod + def is_admin() -> bool: + """检查是否具有管理员权限""" + try: + return ctypes.windll.shell32.IsUserAnAdmin() + except: + return False + + def _get_storage_content(self) -> Dict[str, Any]: + """读取storage.json的内容""" + try: + with open(self.storage_file, 'r', encoding='utf-8') as f: + return json.load(f) + except Exception as e: + raise Exception(f"读取配置文件失败: {str(e)}") + + def _save_storage_content(self, content: Dict[str, Any]) -> None: + """保存内容到storage.json""" + try: + with open(self.storage_file, 'w', encoding='utf-8') as f: + json.dump(content, f, indent=2, ensure_ascii=False) + except Exception as e: + raise Exception(f"保存配置文件失败: {str(e)}") + + def backup_file(self) -> str: + """ + 备份配置文件 + :return: 备份文件路径 + """ + if not os.path.exists(self.storage_file): + raise FileNotFoundError(f"配置文件不存在:{self.storage_file}") + + backup_name = f"storage.json.backup_{datetime.now().strftime('%Y%m%d_%H%M%S')}" + backup_path = os.path.join(self.backup_dir, backup_name) + shutil.copy2(self.storage_file, backup_path) + self.logger.info(f"配置已备份到: {backup_path}") + return backup_path + + def generate_new_ids(self) -> Dict[str, str]: + """ + 生成新的机器码系列 + :return: 包含新ID的字典 + """ + # 生成 auth0|user_ 前缀的十六进制 + prefix = "auth0|user_".encode('utf-8').hex() + # 生成32字节(64个十六进制字符)的随机数 + random_part = uuid.uuid4().hex + uuid.uuid4().hex + + return { + "machineId": f"{prefix}{random_part}", + "macMachineId": str(uuid.uuid4()), + "devDeviceId": str(uuid.uuid4()), + "sqmId": "{" + str(uuid.uuid4()).upper() + "}" + } + + def update_config(self, new_ids: Dict[str, str]) -> None: + """ + 更新配置文件 + :param new_ids: 新的ID字典 + """ + config_content = self._get_storage_content() + + # 更新配置 + config_content['telemetry.machineId'] = new_ids['machineId'] + config_content['telemetry.macMachineId'] = new_ids['macMachineId'] + config_content['telemetry.devDeviceId'] = new_ids['devDeviceId'] + config_content['telemetry.sqmId'] = new_ids['sqmId'] + + # 保存更新后的配置 + self._save_storage_content(config_content) + self.logger.info("配置文件更新成功") + + def update_machine_guid(self) -> str: + """ + 更新注册表中的MachineGuid + :return: 新的GUID + """ + reg_path = r"SOFTWARE\Microsoft\Cryptography" + try: + # 打开注册表键 + reg_key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, reg_path, 0, + winreg.KEY_WRITE | winreg.KEY_READ) + + # 获取当前GUID用于备份 + current_guid, _ = winreg.QueryValueEx(reg_key, "MachineGuid") + self.logger.info(f"当前MachineGuid: {current_guid}") + + # 生成新GUID + new_guid = str(uuid.uuid4()) + + # 更新注册表 + winreg.SetValueEx(reg_key, "MachineGuid", 0, winreg.REG_SZ, new_guid) + + # 验证更改 + updated_guid, _ = winreg.QueryValueEx(reg_key, "MachineGuid") + if updated_guid != new_guid: + raise Exception("注册表验证失败,值未成功更新") + + winreg.CloseKey(reg_key) + self.logger.info(f"新MachineGuid: {new_guid}") + return new_guid + + except Exception as e: + raise Exception(f"更新MachineGuid失败: {str(e)}") + + def run(self) -> Tuple[Dict[str, str], str]: + """ + 执行重置操作 + :return: (新ID字典, 新GUID) + """ + try: + # 检查管理员权限 + self._update_progress("checking", "检查管理员权限...") + if not self.is_admin(): + raise PermissionError("必须以管理员权限运行该程序") + + # 先退出 Cursor 进程 + self._update_progress("exiting", "正在关闭 Cursor 进程...") + if not ExitCursor(): + raise Exception("无法完全关闭 Cursor 进程,请手动关闭后重试") + + # 备份配置文件 + self._update_progress("backup", "备份配置文件...") + backup_path = self.backup_file() + self.logger.info(f"配置已备份到: {backup_path}") + + # 生成新机器码 + self._update_progress("generating", "生成新的机器码...") + new_ids = self.generate_new_ids() + + # 更新配置文件 + self._update_progress("updating", "更新配置文件...") + self.update_config(new_ids) + + # 更新注册表 + self._update_progress("registry", "更新注册表...") + new_guid = self.update_machine_guid() + + self._update_progress("complete", "重置完成") + self.logger.info("机器码重置成功") + + return new_ids, new_guid + + except Exception as e: + self.logger.error(f"重置失败: {str(e)}") + self._update_progress("error", f"操作失败: {str(e)}") + raise \ No newline at end of file diff --git a/services/__pycache__/cursor_service.cpython-312.pyc b/services/__pycache__/cursor_service.cpython-312.pyc index f42a2d8..7733398 100644 Binary files a/services/__pycache__/cursor_service.cpython-312.pyc and b/services/__pycache__/cursor_service.cpython-312.pyc differ diff --git a/services/cursor_service.py b/services/cursor_service.py index cb6a196..8d71d9f 100644 --- a/services/cursor_service.py +++ b/services/cursor_service.py @@ -200,7 +200,7 @@ class CursorService: try: new_ids, new_guid = self.machine_resetter.run() - return True, "重置成功", { + return True, "重置机器码成功请重启cursor编辑器", { "new_ids": new_ids, "new_guid": new_guid } diff --git a/testbuild.bat b/testbuild.bat index d19f49f..bebe3e8 100644 --- a/testbuild.bat +++ b/testbuild.bat @@ -58,9 +58,15 @@ del /s /q *.pyc >nul 2>&1 del /s /q *.pyo >nul 2>&1 :: 清理旧的打包文件 -echo 清理旧文件... -if exist "build" rd /s /q "build" +:: 保留当前版本的 .spec 文件 +set CURRENT_SPEC=dist\听泉cursor助手_test.spec +if exist "!CURRENT_SPEC!" ( + move "!CURRENT_SPEC!" "!CURRENT_SPEC!.bak" +) if exist "*.spec" del /f /q "*.spec" +if exist "!CURRENT_SPEC!.bak" ( + move "!CURRENT_SPEC!.bak" "!CURRENT_SPEC!" +) :: 使用优化选项进行打包 echo 开始打包... diff --git a/update_disabler.py b/update_disabler.py index dbc83f1..c91655a 100644 --- a/update_disabler.py +++ b/update_disabler.py @@ -44,10 +44,10 @@ class UpdateDisabler: if not os.path.exists(parent_dir): os.makedirs(parent_dir, exist_ok=True) - def disable(self) -> bool: + def disable(self) -> Tuple[bool, str]: """ 禁用自动更新 - :return: 是否成功 + :return: (是否成功, 消息) """ try: self._update_progress("start", "开始禁用自动更新...") @@ -123,13 +123,14 @@ class UpdateDisabler: self._update_progress("complete", "禁用自动更新完成") self.logger.info("成功禁用自动更新") - return True + return True, "禁用自动更新成功" except Exception as e: self.logger.error(f"禁用自动更新失败: {str(e)}") self._update_progress("error", f"操作失败: {str(e)}") - self.show_manual_guide() - return False + + # 返回权限不足的错误,触发统一的解决方案提示 + return False, "权限不足,无法禁用更新\n这是正常现象,不影响使用\n您可以点击【解决方案】按钮来解决此问题" def show_manual_guide(self) -> str: """ diff --git a/utils/__pycache__/version_manager.cpython-312.pyc b/utils/__pycache__/version_manager.cpython-312.pyc index 761dfe1..fabe1d3 100644 Binary files a/utils/__pycache__/version_manager.cpython-312.pyc and b/utils/__pycache__/version_manager.cpython-312.pyc differ diff --git a/utils/version_manager.py b/utils/version_manager.py index 3b0b29a..a810790 100644 --- a/utils/version_manager.py +++ b/utils/version_manager.py @@ -147,8 +147,8 @@ class VersionManager: if update_info.get("has_update"): # 有更新可用 version_info = update_info.get("version_info", {}) - new_version = version_info.get("version") - update_msg = version_info.get("update_msg", "") + new_version = version_info.get("version_no") + update_msg = version_info.get("description", "") download_url = version_info.get("download_url", "") is_force = update_info.get("is_force", 0) diff --git a/version.txt b/version.txt index 7636e75..6aba2b2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.0.5 +4.2.0 diff --git a/听泉助手v4.1.7.spec b/听泉助手v4.1.7.spec new file mode 100644 index 0000000..ecf7183 --- /dev/null +++ b/听泉助手v4.1.7.spec @@ -0,0 +1,44 @@ +# -*- mode: python ; coding: utf-8 -*- +from PyInstaller.utils.hooks import collect_submodules + +hiddenimports = ['json', 'sqlite3', 'winreg', 'ctypes', 'platform', 'uuid', 'hashlib', 'datetime', 'urllib3', 'requests', 'PyQt5', 'PyQt5.sip', 'psutil', 'psutil._psutil_windows', 'psutil._pswindows'] +hiddenimports += collect_submodules('psutil') + + +a = Analysis( + ['tingquan_assistant.py'], + pathex=[], + binaries=[], + datas=[('version.txt', '.'), ('requirements.txt', '.'), ('two.ico', '.'), ('config.py', '.'), ('logger.py', '.'), ('common_utils.py', '.'), ('cursor_token_refresher.py', '.'), ('machine_resetter.py', '.'), ('update_disabler.py', '.'), ('exit_cursor.py', '.'), ('gui', 'gui'), ('services', 'services'), ('utils', 'utils')], + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=['_tkinter', 'tkinter', 'PIL.ImageTk', 'PIL.ImageWin', 'numpy', 'pandas', 'matplotlib', '__pycache__', '*.pyc', '*.pyo', '*.pyd'], + noarchive=False, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='听泉助手v4.1.7', + 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, + version='file_version_info.txt', + uac_admin=True, + icon=['two.ico'], +) diff --git a/听泉助手v4.1.8.spec b/听泉助手v4.1.8.spec new file mode 100644 index 0000000..706c034 --- /dev/null +++ b/听泉助手v4.1.8.spec @@ -0,0 +1,44 @@ +# -*- mode: python ; coding: utf-8 -*- +from PyInstaller.utils.hooks import collect_submodules + +hiddenimports = ['json', 'sqlite3', 'winreg', 'ctypes', 'platform', 'uuid', 'hashlib', 'datetime', 'urllib3', 'requests', 'PyQt5', 'PyQt5.sip', 'psutil', 'psutil._psutil_windows', 'psutil._pswindows'] +hiddenimports += collect_submodules('psutil') + + +a = Analysis( + ['tingquan_assistant.py'], + pathex=[], + binaries=[], + datas=[('version.txt', '.'), ('requirements.txt', '.'), ('two.ico', '.'), ('config.py', '.'), ('logger.py', '.'), ('common_utils.py', '.'), ('cursor_token_refresher.py', '.'), ('machine_resetter.py', '.'), ('update_disabler.py', '.'), ('exit_cursor.py', '.'), ('gui', 'gui'), ('services', 'services'), ('utils', 'utils')], + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=['_tkinter', 'tkinter', 'PIL.ImageTk', 'PIL.ImageWin', 'numpy', 'pandas', 'matplotlib', '__pycache__', '*.pyc', '*.pyo', '*.pyd'], + noarchive=False, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='听泉助手v4.1.8', + 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, + version='file_version_info.txt', + uac_admin=True, + icon=['two.ico'], +) diff --git a/听泉助手v4.1.9.spec b/听泉助手v4.1.9.spec new file mode 100644 index 0000000..25d28f9 --- /dev/null +++ b/听泉助手v4.1.9.spec @@ -0,0 +1,44 @@ +# -*- mode: python ; coding: utf-8 -*- +from PyInstaller.utils.hooks import collect_submodules + +hiddenimports = ['json', 'sqlite3', 'winreg', 'ctypes', 'platform', 'uuid', 'hashlib', 'datetime', 'urllib3', 'requests', 'PyQt5', 'PyQt5.sip', 'psutil', 'psutil._psutil_windows', 'psutil._pswindows'] +hiddenimports += collect_submodules('psutil') + + +a = Analysis( + ['tingquan_assistant.py'], + pathex=[], + binaries=[], + datas=[('version.txt', '.'), ('requirements.txt', '.'), ('two.ico', '.'), ('config.py', '.'), ('logger.py', '.'), ('common_utils.py', '.'), ('cursor_token_refresher.py', '.'), ('machine_resetter.py', '.'), ('update_disabler.py', '.'), ('exit_cursor.py', '.'), ('gui', 'gui'), ('services', 'services'), ('utils', 'utils')], + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=['_tkinter', 'tkinter', 'PIL.ImageTk', 'PIL.ImageWin', 'numpy', 'pandas', 'matplotlib', '__pycache__', '*.pyc', '*.pyo', '*.pyd'], + noarchive=False, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='听泉助手v4.1.9', + 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, + version='file_version_info.txt', + uac_admin=True, + icon=['two.ico'], +) diff --git a/听泉助手v4.2.0.spec b/听泉助手v4.2.0.spec new file mode 100644 index 0000000..476220b --- /dev/null +++ b/听泉助手v4.2.0.spec @@ -0,0 +1,44 @@ +# -*- mode: python ; coding: utf-8 -*- +from PyInstaller.utils.hooks import collect_submodules + +hiddenimports = ['json', 'sqlite3', 'winreg', 'ctypes', 'platform', 'uuid', 'hashlib', 'datetime', 'urllib3', 'requests', 'PyQt5', 'PyQt5.sip', 'psutil', 'psutil._psutil_windows', 'psutil._pswindows'] +hiddenimports += collect_submodules('psutil') + + +a = Analysis( + ['tingquan_assistant.py'], + pathex=[], + binaries=[], + datas=[('version.txt', '.'), ('requirements.txt', '.'), ('two.ico', '.'), ('config.py', '.'), ('logger.py', '.'), ('common_utils.py', '.'), ('cursor_token_refresher.py', '.'), ('machine_resetter.py', '.'), ('update_disabler.py', '.'), ('exit_cursor.py', '.'), ('gui', 'gui'), ('services', 'services'), ('utils', 'utils')], + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=['_tkinter', 'tkinter', 'PIL.ImageTk', 'PIL.ImageWin', 'numpy', 'pandas', 'matplotlib', '__pycache__', '*.pyc', '*.pyo', '*.pyd'], + noarchive=False, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + a.binaries, + a.datas, + [], + name='听泉助手v4.2.0', + 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, + version='file_version_info.txt', + uac_admin=True, + icon=['two.ico'], +)