Files
nezhacursor/account_switcher.py

693 lines
29 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import json
import requests
import logging
import subprocess
import uuid
import hashlib
import sys
import time
from typing import Optional, Dict, Tuple
from pathlib import Path
from utils.config import Config
from utils.cursor_registry import CursorRegistry
from cursor_auth_manager import CursorAuthManager
def get_hardware_id() -> 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())
class AccountSwitcher:
def __init__(self):
self.cursor_path = Path(os.path.expanduser("~")) / "AppData" / "Local" / "Programs" / "Cursor"
self.app_path = self.cursor_path / "resources" / "app"
self.package_json = self.app_path / "package.json"
self.auth_manager = CursorAuthManager()
self.config = Config()
self.hardware_id = get_hardware_id()
self.registry = CursorRegistry() # 添加注册表操作工具类
def get_cursor_version(self) -> str:
"""获取Cursor版本号"""
try:
if self.package_json.exists():
with open(self.package_json, "r", encoding="utf-8") as f:
data = json.load(f)
return data.get("version", "未知")
return "未知"
except Exception as e:
logging.error(f"获取Cursor版本失败: {str(e)}")
return "未知"
def get_device_info(self) -> dict:
"""获取设备信息"""
try:
import platform
import socket
import requests
# 获取操作系统信息
os_info = f"{platform.system()} {platform.version()}"
# 获取设备名称
device_name = platform.node()
# 获取地理位置(可选)
try:
ip_info = requests.get('https://ipapi.co/json/', timeout=5).json()
location = f"{ip_info.get('country_name', '')}-{ip_info.get('region', '')}-{ip_info.get('city', '')}"
except:
location = ""
return {
"os": os_info,
"device_name": device_name,
"location": location
}
except Exception as e:
logging.error(f"获取设备信息失败: {str(e)}")
return {
"os": "Windows 10",
"device_name": "未知设备",
"location": ""
}
def check_activation_code(self, code: str) -> Tuple[bool, str, Optional[Dict]]:
"""验证激活码
Returns:
Tuple[bool, str, Optional[Dict]]: (是否成功, 提示消息, 账号信息)
"""
try:
# 获取当前状态
member_info = self.config.load_member_info()
# 分割多个激活码
codes = [c.strip() for c in code.split(',')]
success_codes = []
failed_codes = []
activation_results = []
# 获取设备信息
device_info = self.get_device_info()
# 逐个验证激活码
for single_code in codes:
if not single_code:
continue
# 验证激活码
endpoint = "https://cursorapi.nosqli.com/admin/api.member/activate"
data = {
"code": single_code,
"machine_id": self.hardware_id,
"os": device_info["os"],
"device_name": device_info["device_name"],
"location": device_info["location"]
}
headers = {
"Content-Type": "application/json"
}
try:
response = requests.post(
endpoint,
json=data,
headers=headers,
timeout=10
)
response_data = response.json()
if response_data.get("code") == 200:
result_data = response_data.get("data", {})
logging.info(f"激活码 {single_code} 验证成功: {response_data.get('msg', '')}")
activation_results.append(result_data)
success_codes.append(single_code)
elif response_data.get("code") == 400:
error_msg = response_data.get("msg", "参数错误")
if "已被使用" in error_msg or "已激活" in error_msg:
logging.warning(f"激活码 {single_code} 已被使用")
failed_codes.append(f"{single_code} (已被使用)")
else:
logging.error(f"激活码 {single_code} 验证失败: {error_msg}")
failed_codes.append(f"{single_code} ({error_msg})")
elif response_data.get("code") == 500:
error_msg = response_data.get("msg", "系统错误")
logging.error(f"激活码 {single_code} 验证失败: {error_msg}")
failed_codes.append(f"{single_code} ({error_msg})")
else:
error_msg = response_data.get("msg", "未知错误")
logging.error(f"激活码 {single_code} 验证失败: {error_msg}")
failed_codes.append(f"{single_code} ({error_msg})")
except requests.RequestException as e:
logging.error(f"激活码 {single_code} 请求失败: {str(e)}")
failed_codes.append(f"{single_code} (网络请求失败)")
except Exception as e:
logging.error(f"激活码 {single_code} 处理失败: {str(e)}")
failed_codes.append(f"{single_code} (处理失败)")
if not success_codes:
failed_msg = "\n".join(failed_codes)
return False, f"激活失败:\n{failed_msg}", None
try:
# 使用最后一次激活的结果作为最终状态
final_result = activation_results[-1]
# 保存会员信息
member_info = {
"hardware_id": final_result.get("machine_id", self.hardware_id),
"expire_time": final_result.get("expire_time", ""),
"days_left": final_result.get("days_left", 0), # 使用days_left
"total_days": final_result.get("total_days", 0), # 使用total_days
"status": final_result.get("status", "inactive"),
"device_info": final_result.get("device_info", device_info),
"activation_time": final_result.get("activation_time", ""),
"activation_records": final_result.get("activation_records", []) # 保存激活记录
}
self.config.save_member_info(member_info)
# 生成结果消息
message = f"激活成功\n"
# 显示每个成功激活码的信息
for i, result in enumerate(activation_results, 1):
message += f"\n{i}个激活码:\n"
message += f"- 新增天数: {result.get('added_days', 0)}\n" # 使用added_days显示本次新增天数
# 格式化时间显示
activation_time = result.get('activation_time', '')
if activation_time:
try:
from datetime import datetime
dt = datetime.strptime(activation_time, "%Y-%m-%d %H:%M:%S")
activation_time = dt.strftime("%Y-%m-%d %H:%M:%S")
except:
pass
message += f"- 激活时间: {activation_time}\n"
message += f"\n最终状态:"
message += f"\n- 总天数: {final_result.get('total_days', 0)}" # 累计总天数
message += f"\n- 剩余天数: {final_result.get('days_left', 0)}" # 剩余天数
# 格式化到期时间显示
expire_time = final_result.get('expire_time', '')
if expire_time:
try:
dt = datetime.strptime(expire_time, "%Y-%m-%d %H:%M:%S")
expire_time = dt.strftime("%Y-%m-%d %H:%M:%S")
except:
pass
message += f"\n- 到期时间: {expire_time}"
# 显示激活记录历史
message += "\n\n历史激活记录:"
for record in final_result.get('activation_records', []):
activation_time = record.get('activation_time', '')
if activation_time:
try:
dt = datetime.strptime(activation_time, "%Y-%m-%d %H:%M:%S")
activation_time = dt.strftime("%Y-%m-%d %H:%M:%S")
except:
pass
message += f"\n- 激活码: {record.get('code', '')}"
message += f"\n 天数: {record.get('days', 0)}"
message += f"\n 时间: {activation_time}\n"
if failed_codes:
message += f"\n\n以下激活码验证失败:\n" + "\n".join(failed_codes)
return True, message, member_info
except Exception as e:
logging.error(f"处理激活结果时出错: {str(e)}")
return False, f"处理激活结果失败: {str(e)}", None
except Exception as e:
logging.error(f"激活码验证过程出错: {str(e)}")
return False, f"激活失败: {str(e)}", None
def reset_machine_id(self) -> bool:
"""重置机器码"""
try:
# 1. 先关闭所有Cursor进程
if sys.platform == "win32":
os.system("taskkill /f /im Cursor.exe >nul 2>&1")
time.sleep(2)
# 2. 删除 package.json 中的 machineId
if self.package_json.exists():
with open(self.package_json, "r", encoding="utf-8") as f:
data = json.load(f)
if "machineId" in data:
del data["machineId"]
with open(self.package_json, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2)
# 3. 清理特定的配置文件和缓存
local_app_data = Path(os.getenv('LOCALAPPDATA'))
cursor_path = local_app_data / "Cursor"
# 需要清理的目录
cache_dirs = [
cursor_path / "Cache",
cursor_path / "Code Cache",
cursor_path / "GPUCache",
cursor_path / "Local Storage" / "leveldb"
]
# 需要删除的配置文件
config_files = [
cursor_path / "User" / "globalStorage" / "storage.json",
cursor_path / "User" / "globalStorage" / "state.json",
self.app_path / "config.json",
self.app_path / "state.json",
self.app_path / "settings.json"
]
# 清理缓存目录
for dir_path in cache_dirs:
if dir_path.exists():
try:
import shutil
shutil.rmtree(str(dir_path))
logging.info(f"清理目录成功: {dir_path}")
except Exception as e:
logging.warning(f"清理目录失败: {dir_path}, 错误: {str(e)}")
# 删除配置文件
for file_path in config_files:
if file_path.exists():
try:
os.remove(file_path)
logging.info(f"删除配置文件成功: {file_path}")
except Exception as e:
logging.warning(f"删除配置文件失败: {file_path}, 错误: {str(e)}")
# 4. 刷新注册表
if not self.registry.refresh_registry():
logging.warning("注册表刷新失败")
# 5. 删除卸载注册表项
try:
import winreg
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, r"Software\Microsoft\Windows\CurrentVersion\Uninstall", 0, winreg.KEY_ALL_ACCESS) as key:
# 遍历所有子键找到Cursor相关的
i = 0
while True:
try:
subkey_name = winreg.EnumKey(key, i)
if "Cursor" in subkey_name:
winreg.DeleteKey(key, subkey_name)
logging.info(f"删除注册表项成功: {subkey_name}")
i += 1
except WindowsError:
break
except Exception as e:
logging.warning(f"删除注册表项失败: {str(e)}")
# 6. 重启Cursor
cursor_exe = self.cursor_path / "Cursor.exe"
if cursor_exe.exists():
os.startfile(str(cursor_exe))
logging.info("Cursor重启成功")
logging.info("机器码重置完成")
return True
except Exception as e:
logging.error(f"重置机器码失败: {str(e)}")
return False
def bypass_version_limit(self) -> Tuple[bool, str]:
"""突破Cursor版本限制"""
try:
# 1. 先关闭所有Cursor进程
if sys.platform == "win32":
os.system("taskkill /f /im Cursor.exe >nul 2>&1")
time.sleep(2)
# 2. 重置机器码
if not self.reset_machine_id():
return False, "重置机器码失败"
# 3. 等待Cursor启动
time.sleep(3)
return True, "突破版本限制成功Cursor已重启"
except Exception as e:
logging.error(f"突破版本限制失败: {str(e)}")
return False, f"突破失败: {str(e)}"
def activate_and_switch(self, activation_code: str) -> Tuple[bool, str]:
"""激活并切换账号
Returns:
Tuple[bool, str]: (是否成功, 提示消息)
"""
try:
# 验证激活码
success, message, account_info = self.check_activation_code(activation_code)
return success, message
except Exception as e:
logging.error(f"激活过程出错: {str(e)}")
return False, f"激活失败: {str(e)}"
def get_member_status(self) -> Optional[Dict]:
"""获取会员状态
Returns:
Optional[Dict]: 会员状态信息
"""
try:
# 读取保存的会员信息
member_info = self.config.load_member_info()
# 构造状态检查请求
endpoint = "https://cursorapi.nosqli.com/admin/api.member/status"
data = {
"machine_id": self.hardware_id
}
headers = {
"Content-Type": "application/json"
}
try:
response = requests.post(endpoint, json=data, headers=headers, timeout=10)
response_data = response.json()
if response_data.get("code") == 200:
# 正常状态
data = response_data.get("data", {})
status = data.get("status", "inactive")
# 构造会员信息
member_info = {
"hardware_id": data.get("machine_id", self.hardware_id),
"expire_time": data.get("expire_time", ""),
"days_left": data.get("days_left", 0), # 使用days_left
"total_days": data.get("total_days", 0), # 使用total_days
"status": status,
"activation_records": data.get("activation_records", []) # 保存激活记录
}
# 打印调试信息
logging.info(f"API返回数据: {data}")
logging.info(f"处理后的会员信息: {member_info}")
self.config.save_member_info(member_info)
return member_info
elif response_data.get("code") == 401:
# 未激活或已过期
logging.warning("会员未激活或已过期")
return self._get_inactive_status()
elif response_data.get("code") == 400:
# 参数错误
error_msg = response_data.get("msg", "参数错误")
logging.error(f"获取会员状态失败: {error_msg}")
return self._get_inactive_status()
elif response_data.get("code") == 500:
# 系统错误
error_msg = response_data.get("msg", "系统错误")
logging.error(f"获取会员状态失败: {error_msg}")
return self._get_inactive_status()
else:
# 其他未知错误
error_msg = response_data.get("msg", "未知错误")
logging.error(f"获取会员状态失败: {error_msg}")
return self._get_inactive_status()
except requests.RequestException as e:
logging.error(f"请求会员状态失败: {str(e)}")
return self._get_inactive_status()
except Exception as e:
logging.error(f"获取会员状态出错: {str(e)}")
return self._get_inactive_status()
def _get_inactive_status(self) -> Dict:
"""获取未激活状态的默认信息"""
return {
"hardware_id": self.hardware_id,
"expire_time": "",
"days": 0,
"total_days": 0,
"status": "inactive",
"last_activation": {},
"activation_records": []
}
def restart_cursor(self) -> bool:
"""重启Cursor编辑器
Returns:
bool: 是否成功重启
"""
try:
logging.info("正在重启Cursor...")
if sys.platform == "win32":
# Windows系统
# 关闭Cursor
os.system("taskkill /f /im Cursor.exe 2>nul")
time.sleep(2)
# 获取Cursor安装路径
cursor_exe = self.cursor_path / "Cursor.exe"
if cursor_exe.exists():
# 启动Cursor
os.startfile(str(cursor_exe))
logging.info("Cursor重启成功")
return True
else:
logging.error(f"未找到Cursor程序: {cursor_exe}")
return False
elif sys.platform == "darwin":
# macOS系统
os.system("killall Cursor 2>/dev/null")
time.sleep(2)
os.system("open -a Cursor")
logging.info("Cursor重启成功")
return True
elif sys.platform == "linux":
# Linux系统
os.system("pkill -f cursor")
time.sleep(2)
os.system("cursor &")
logging.info("Cursor重启成功")
return True
else:
logging.error(f"不支持的操作系统: {sys.platform}")
return False
except Exception as e:
logging.error(f"重启Cursor时发生错误: {str(e)}")
return False
def refresh_cursor_auth(self) -> Tuple[bool, str]:
"""刷新Cursor授权
Returns:
Tuple[bool, str]: (是否成功, 提示消息)
"""
try:
# 获取未使用的账号
endpoint = "https://cursorapi.nosqli.com/admin/api.account/getUnused"
data = {
"machine_id": self.hardware_id
}
headers = {
"Content-Type": "application/json"
}
try:
# 添加SSL验证选项和超时设置
response = requests.post(
endpoint,
json=data,
headers=headers,
timeout=30, # 增加超时时间
verify=False, # 禁用SSL验证
)
# 禁用SSL警告
requests.packages.urllib3.disable_warnings()
response_data = response.json()
if response_data.get("code") == 200:
account_data = response_data.get("data", {})
# 获取账号信息
email = account_data.get("email", "")
access_token = account_data.get("access_token", "")
refresh_token = account_data.get("refresh_token", "")
expire_time = account_data.get("expire_time", "")
days_left = account_data.get("days_left", 0)
if not all([email, access_token, refresh_token]):
return False, "获取账号信息不完整"
# 更新Cursor认证信息
if not self.auth_manager.update_auth(email, access_token, refresh_token):
return False, "更新Cursor认证信息失败"
# 重置机器码
if not self.reset_machine_id():
return False, "重置机器码失败"
# 刷新注册表
if not self.registry.refresh_registry():
logging.warning("注册表刷新失败,但不影响主要功能")
# 重启Cursor
if not self.auth_manager.restart_cursor():
return False, "重启Cursor失败"
# 重启Cursor
# if not self.restart_cursor():
# logging.warning("Cursor重启失败请手动重启")
# return True, f"授权刷新成功请手动重启Cursor编辑器\n邮箱: {email}\n到期时间: {expire_time}\n剩余天数: {days_left}天"
return True, f"授权刷新成功Cursor编辑器已重启\n邮箱: {email}\n"
elif response_data.get("code") == 404:
return False, "没有可用的未使用账号"
else:
error_msg = response_data.get("msg", "未知错误")
logging.error(f"获取未使用账号失败: {error_msg}")
return False, f"获取账号失败: {error_msg}"
except requests.exceptions.SSLError as e:
logging.error(f"SSL验证失败: {str(e)}")
return False, "SSL验证失败,请检查网络设置"
except requests.exceptions.ConnectionError as e:
logging.error(f"网络连接错误: {str(e)}")
return False, "网络连接失败,请检查网络设置"
except requests.exceptions.Timeout as e:
logging.error(f"请求超时: {str(e)}")
return False, "请求超时,请稍后重试"
except requests.RequestException as e:
logging.error(f"请求失败: {str(e)}")
return False, f"网络请求失败: {str(e)}"
except Exception as e:
logging.error(f"未知错误: {str(e)}")
return False, f"发生未知错误: {str(e)}"
except Exception as e:
logging.error(f"刷新授权过程出错: {str(e)}")
return False, f"刷新失败: {str(e)}"
def disable_cursor_update(self) -> Tuple[bool, str]:
"""禁用Cursor更新"""
try:
# 1. 先关闭所有Cursor进程
if sys.platform == "win32":
os.system("taskkill /f /im Cursor.exe >nul 2>&1")
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():
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)}"
except Exception as e:
logging.error(f"禁用Cursor更新失败: {str(e)}")
return False, f"禁用更新失败: {str(e)}"
def main():
"""主函数"""
try:
switcher = AccountSwitcher()
print("\n=== Cursor账号切换工具 ===")
print("1. 激活并切换账号")
print("2. 仅重置机器码")
while True:
try:
choice = int(input("\n请选择操作 (1 或 2): ").strip())
if choice in [1, 2]:
break
else:
print("无效的选项,请重新输入")
except ValueError:
print("请输入有效的数字")
if choice == 1:
activation_code = input("请输入激活码: ").strip()
if switcher.activate_and_switch(activation_code):
print("\n账号激活成功!")
else:
print("\n账号激活失败,请查看日志了解详细信息")
else:
if switcher.reset_machine_id():
print("\n机器码重置成功!")
else:
print("\n机器码重置失败,请查看日志了解详细信息")
except Exception as e:
logging.error(f"程序执行出错: {str(e)}")
print("\n程序执行出错,请查看日志了解详细信息")
finally:
input("\n按回车键退出...")
if __name__ == "__main__":
main()