commit 49fa4d6fd632fddaee6f1a69069a407bb4c33bb7 Author: Your Name Date: Sun Feb 9 22:02:44 2025 +0800 first commit diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..de6a57c --- /dev/null +++ b/.env.example @@ -0,0 +1,16 @@ +# 你的CF路由填写的域名 +DOMAIN=xxxxx.me +# 邮件服务地址 +# 注册临时邮件服务 https://tempmail.plus +TEMP_MAIL=xxxxxx +# 设置的PIN码 +TEMP_MAIL_EPIN=xxxxxx +# 使用的后缀 +TEMP_MAIL_EXT=@mailto.plus +BROWSER_USER_AGENT=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.92 Safari/537.36 + +# 代理 +# BROWSER_PROXY='http://127.0.0.1:2080' + +# 无头模式 默认开启 +# BROWSER_HEADLESS='True' diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2b0590d --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# PyInstaller +build/ +dist/ +*.spec +!CursorKeepAlive.mac.spec +!CursorKeepAlive.win.spec + +# Python +__pycache__/ +*.py[cod] +*$py.class + +# Logs +*.log + +# IDE +.vscode/ +.idea/ + +# Mac +.DS_Store + +venv/ + +node_modules/ + +.env + +screenshots/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..49cc8ef Binary files /dev/null and b/README.md differ diff --git a/browser_utils.py b/browser_utils.py new file mode 100644 index 0000000..19cb709 --- /dev/null +++ b/browser_utils.py @@ -0,0 +1,68 @@ +from DrissionPage import ChromiumOptions, Chromium +import sys +import os +import logging +from config import Config + +class BrowserManager: + def __init__(self): + self.browser = None + self.config = Config() + + def init_browser(self, user_agent=None): + """初始化浏览器""" + co = self._get_browser_options(user_agent) + self.browser = Chromium(co) + return self.browser + + def _get_browser_options(self, user_agent=None): + """获取浏览器配置""" + co = ChromiumOptions() + try: + extension_path = self._get_extension_path() + co.add_extension(extension_path) + except FileNotFoundError as e: + logging.warning(f"警告: {e}") + + co.set_pref("credentials_enable_service", False) + co.set_argument("--hide-crash-restore-bubble") + + # 从配置中获取代理设置 + proxy = self.config.get_proxy() if hasattr(self.config, 'get_proxy') else None + if proxy: + co.set_proxy(proxy) + + co.auto_port() + if user_agent: + co.set_user_agent(user_agent) + + # 设置为无头模式 + co.headless(True) + + # 基础设置 + co.set_argument("--no-sandbox") + co.set_argument("--disable-gpu") + co.set_argument("--disable-dev-shm-usage") + + return co + + def _get_extension_path(self): + """获取插件路径""" + root_dir = os.getcwd() + extension_path = os.path.join(root_dir, "turnstilePatch") + + if hasattr(sys, "_MEIPASS"): + extension_path = os.path.join(sys._MEIPASS, "turnstilePatch") + + if not os.path.exists(extension_path): + raise FileNotFoundError(f"插件不存在: {extension_path}") + + return extension_path + + def quit(self): + """关闭浏览器""" + if self.browser: + try: + self.browser.quit() + except: + pass diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..3249dca --- /dev/null +++ b/build.bat @@ -0,0 +1,32 @@ +@echo off +set PYTHONWARNINGS=ignore::SyntaxWarning:DrissionPage +echo Building Cursor Keep Alive... + +:: Check if virtual environment exists +if not exist "venv" ( + python -m venv venv + if errorlevel 1 ( + echo Failed to create virtual environment! + exit /b 1 + ) +) + +:: Activate virtual environment and wait for activation to complete +call venv\Scripts\activate.bat +timeout /t 2 /nobreak > nul + +:: Install dependencies +echo Installing dependencies... +python -m pip install --upgrade pip +pip install -r requirements.txt + +:: Run build script +echo Starting build process... +python build.py + +:: Deactivate virtual environment +deactivate + +:: Keep window open +echo Build completed! +pause \ No newline at end of file diff --git a/build.mac.command b/build.mac.command new file mode 100644 index 0000000..6fd4bb4 --- /dev/null +++ b/build.mac.command @@ -0,0 +1,33 @@ +#!/bin/bash +export PYTHONWARNINGS=ignore::SyntaxWarning:DrissionPage + +# Get script directory +cd "$(dirname "$0")" + +echo "Creating virtual environment..." + +# Check if virtual environment exists +if [ ! -d "venv" ]; then + python3 -m venv venv + if [ $? -ne 0 ]; then + echo "Failed to create virtual environment!" + exit 1 + fi +fi + +# Activate virtual environment +source venv/bin/activate + +# Install dependencies +echo "Installing dependencies..." +python -m pip install --upgrade pip +pip install -r requirements.txt + +# Run build script +echo "Starting build process..." +python build.py + +# Keep window open +echo "Build completed!" +echo "Press any key to exit..." +read -n 1 \ No newline at end of file diff --git a/build.py b/build.py new file mode 100644 index 0000000..2bc5e4f --- /dev/null +++ b/build.py @@ -0,0 +1,179 @@ +import warnings +import os +import platform +import subprocess +import time +import threading + +# Ignore specific SyntaxWarning +warnings.filterwarnings("ignore", category=SyntaxWarning, module="DrissionPage") + +CURSOR_LOGO = """ + ██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ + ██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ + ██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ + ██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ + ╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ + ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ +""" + + +class LoadingAnimation: + def __init__(self): + self.is_running = False + self.animation_thread = None + + def start(self, message="Building"): + self.is_running = True + self.animation_thread = threading.Thread(target=self._animate, args=(message,)) + self.animation_thread.start() + + def stop(self): + self.is_running = False + if self.animation_thread: + self.animation_thread.join() + print("\r" + " " * 70 + "\r", end="", flush=True) # Clear the line + + def _animate(self, message): + animation = "|/-\\" + idx = 0 + while self.is_running: + print(f"\r{message} {animation[idx % len(animation)]}", end="", flush=True) + idx += 1 + time.sleep(0.1) + + +def print_logo(): + print("\033[96m" + CURSOR_LOGO + "\033[0m") + print("\033[93m" + "Building Cursor Keep Alive...".center(56) + "\033[0m\n") + + +def progress_bar(progress, total, prefix="", length=50): + filled = int(length * progress // total) + bar = "█" * filled + "░" * (length - filled) + percent = f"{100 * progress / total:.1f}" + print(f"\r{prefix} |{bar}| {percent}% Complete", end="", flush=True) + if progress == total: + print() + + +def simulate_progress(message, duration=1.0, steps=20): + print(f"\033[94m{message}\033[0m") + for i in range(steps + 1): + time.sleep(duration / steps) + progress_bar(i, steps, prefix="Progress:", length=40) + + +def filter_output(output): + """ImportantMessage""" + if not output: + return "" + important_lines = [] + for line in output.split("\n"): + # Only keep lines containing specific keywords + if any( + keyword in line.lower() + for keyword in ["error:", "failed:", "completed", "directory:"] + ): + important_lines.append(line) + return "\n".join(important_lines) + + +def build(): + # Clear screen + os.system("cls" if platform.system().lower() == "windows" else "clear") + + # Print logo + print_logo() + + system = platform.system().lower() + spec_file = os.path.join("CursorKeepAlive.spec") + + # if system not in ["darwin", "windows"]: + # print(f"\033[91mUnsupported operating system: {system}\033[0m") + # return + + output_dir = f"dist/{system if system != 'darwin' else 'mac'}" + + # Create output directory + os.makedirs(output_dir, exist_ok=True) + simulate_progress("Creating output directory...", 0.5) + + # Run PyInstaller with loading animation + pyinstaller_command = [ + "pyinstaller", + spec_file, + "--distpath", + output_dir, + "--workpath", + f"build/{system}", + "--noconfirm", + ] + + loading = LoadingAnimation() + try: + simulate_progress("Running PyInstaller...", 2.0) + loading.start("Building in progress") + result = subprocess.run( + pyinstaller_command, check=True, capture_output=True, text=True + ) + loading.stop() + + if result.stderr: + filtered_errors = [ + line + for line in result.stderr.split("\n") + if any( + keyword in line.lower() + for keyword in ["error:", "failed:", "completed", "directory:"] + ) + ] + if filtered_errors: + print("\033[93mBuild Warnings/Errors:\033[0m") + print("\n".join(filtered_errors)) + + except subprocess.CalledProcessError as e: + loading.stop() + print(f"\033[91mBuild failed with error code {e.returncode}\033[0m") + if e.stderr: + print("\033[91mError Details:\033[0m") + print(e.stderr) + return + except FileNotFoundError: + loading.stop() + print( + "\033[91mError: Please ensure PyInstaller is installed (pip install pyinstaller)\033[0m" + ) + return + except KeyboardInterrupt: + loading.stop() + print("\n\033[91mBuild cancelled by user\033[0m") + return + finally: + loading.stop() + + # Copy config file + if os.path.exists("config.ini.example"): + simulate_progress("Copying configuration file...", 0.5) + if system == "windows": + subprocess.run( + ["copy", "config.ini.example", f"{output_dir}\\config.ini"], shell=True + ) + else: + subprocess.run(["cp", "config.ini.example", f"{output_dir}/config.ini"]) + + # Copy .env.example file + if os.path.exists(".env.example"): + simulate_progress("Copying environment file...", 0.5) + if system == "windows": + subprocess.run(["copy", ".env.example", f"{output_dir}\\.env"], shell=True) + else: + subprocess.run(["cp", ".env.example", f"{output_dir}/.env"]) + + print( + f"\n\033[92mBuild completed successfully! Output directory: {output_dir}\033[0m" + ) + + +if __name__ == "__main__": + build() diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..a9e477e --- /dev/null +++ b/build.sh @@ -0,0 +1,28 @@ +#!/bin/bash +export PYTHONWARNINGS=ignore::SyntaxWarning:DrissionPage + +echo "Creating virtual environment..." + +# Check if virtual environment exists +if [ ! -d "venv" ]; then + python3 -m venv venv + if [ $? -ne 0 ]; then + echo "Failed to create virtual environment!" + exit 1 + fi +fi + +# Activate virtual environment +source venv/bin/activate + +# Install dependencies +echo "Installing dependencies..." +python -m pip install --upgrade pip +pip install -r requirements.txt + +# Run build script +echo "Starting build process..." +python build.py + +# Complete +echo "Build completed!" \ No newline at end of file diff --git a/config.py b/config.py new file mode 100644 index 0000000..71ea467 --- /dev/null +++ b/config.py @@ -0,0 +1,181 @@ +import os +import sys +import requests +import json +import time +from logger import logging +import urllib3 +from requests.adapters import HTTPAdapter +from urllib3.util.retry import Retry + +# 禁用 SSL 警告 +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +class Config: + def __init__(self): + # 获取应用程序的根目录路径 + if getattr(sys, "frozen", False): + # 如果是打包后的可执行文件 + application_path = os.path.dirname(sys.executable) + else: + # 如果是开发环境 + application_path = os.path.dirname(os.path.abspath(__file__)) + + # 配置重试策略 + retry_strategy = Retry( + total=3, + backoff_factor=1, + status_forcelist=[429, 500, 502, 503, 504], + ) + + # 创建共享session + self.session = requests.Session() + self.session.trust_env = False # 不使用系统代理设置 + self.session.verify = False + self.session.proxies = {} # 完全禁用代理 + + # 配置adapter + adapter = HTTPAdapter(max_retries=retry_strategy) + self.session.mount("https://", adapter) + self.session.mount("http://", adapter) + + # 从API获取配置 + try: + # 使用session发送请求 + response = self.session.get( + "https://cursorapi.nosqli.com/admin/api.mail/getRandom", + timeout=30, + allow_redirects=True + ) + data = response.json() + + if data['code'] != 0: + raise Exception(data['msg']) + + config = data['data']['env'] + + # 设置配置项 + self.imap = False + self.temp_mail = config.get("TEMP_MAIL", "").strip() + self.temp_mail_ext = config.get("TEMP_MAIL_EXT", "").strip() + self.temp_mail_epin = config.get("TEMP_MAIL_EPIN", "").strip() + self.domain = config.get("DOMAIN", "").strip() + self.browser_user_agent = config.get("BROWSER_USER_AGENT", "").strip() + self.mail_server = config.get("MAIL_SERVER", "").strip() + + # 如果临时邮箱为null则加载IMAP + if self.temp_mail == "null": + self.imap = True + self.imap_server = config.get("IMAP_SERVER", "").strip() + self.imap_port = config.get("IMAP_PORT", "").strip() + self.imap_user = config.get("IMAP_USER", "").strip() + self.imap_pass = config.get("IMAP_PASS", "").strip() + self.imap_dir = config.get("IMAP_DIR", "inbox").strip() + except Exception as e: + logging.error(f"从API获取配置失败: {e}") + raise e + + self.check_config() + + def get_temp_mail(self): + return self.temp_mail.split("@")[0] if "@" in self.temp_mail else self.temp_mail + + def get_temp_mail_ext(self): + return self.temp_mail_ext + + def get_temp_mail_epin(self): + return self.temp_mail_epin + + def get_browser_user_agent(self): + return self.browser_user_agent + + def get_mail_server(self): + return self.mail_server + + def get_imap(self): + if not self.imap: + return False + return { + "imap_server": self.imap_server, + "imap_port": self.imap_port, + "imap_user": self.imap_user, + "imap_pass": self.imap_pass, + "imap_dir": self.imap_dir, + } + + def get_domain(self): + return self.domain + + def check_config(self): + """检查配置项是否有效 + + 检查规则: + 1. 如果使用 tempmail.plus,需要配置 TEMP_MAIL 和 DOMAIN + 2. 如果使用 IMAP,需要配置 IMAP_SERVER、IMAP_PORT、IMAP_USER、IMAP_PASS + """ + # 定义必需的配置项 + required_configs = { + "domain": "域名", + } + + # 检查基础配置 + for key, name in required_configs.items(): + if not self.check_is_valid(getattr(self, key)): + raise ValueError(f"{name}未配置") + + # 检查邮箱配置 + if self.temp_mail != "null": + # tempmail.plus 模式 + if not self.check_is_valid(self.temp_mail): + raise ValueError("临时邮箱未配置") + else: + # IMAP 模式 + imap_configs = { + "imap_server": "IMAP服务器", + "imap_port": "IMAP端口", + "imap_user": "IMAP用户名", + "imap_pass": "IMAP密码", + } + + for key, name in imap_configs.items(): + value = getattr(self, key) + if value == "null" or not self.check_is_valid(value): + raise ValueError(f"{name}未配置") + + # IMAP_DIR 是可选的,如果设置了就检查其有效性 + if self.imap_dir != "null" and not self.check_is_valid(self.imap_dir): + raise ValueError("IMAP收件箱目录配置无效") + + def check_is_valid(self, value): + """检查配置项是否有效 + + Args: + value: 配置项的值 + + Returns: + bool: 配置项是否有效 + """ + return isinstance(value, str) and len(str(value).strip()) > 0 + + def print_config(self): + if self.imap: + logging.info(f"\033[32mIMAP服务器: {self.imap_server}\033[0m") + logging.info(f"\033[32mIMAP端口: {self.imap_port}\033[0m") + logging.info(f"\033[32mIMAP用户名: {self.imap_user}\033[0m") + logging.info(f"\033[32mIMAP密码: {'*' * len(self.imap_pass)}\033[0m") + logging.info(f"\033[32mIMAP收件箱目录: {self.imap_dir}\033[0m") + if self.temp_mail != "null": + logging.info( + f"\033[32m临时邮箱: {self.temp_mail}{self.temp_mail_ext}\033[0m" + ) + logging.info(f"\033[32m域名: {self.domain}\033[0m") + + +# 使用示例 +if __name__ == "__main__": + try: + config = Config() + print("环境变量加载成功!") + config.print_config() + except ValueError as e: + print(f"错误: {e}") diff --git a/cursor_auth_manager.py b/cursor_auth_manager.py new file mode 100644 index 0000000..3ad5579 --- /dev/null +++ b/cursor_auth_manager.py @@ -0,0 +1,86 @@ +import sqlite3 +import os +import sys + + +class CursorAuthManager: + """Cursor认证信息管理器""" + + def __init__(self): + # 判断操作系统 + if sys.platform == "win32": # Windows + appdata = os.getenv("APPDATA") + if appdata is None: + raise EnvironmentError("APPDATA 环境变量未设置") + self.db_path = os.path.join( + appdata, "Cursor", "User", "globalStorage", "state.vscdb" + ) + elif sys.platform == "darwin": # macOS + self.db_path = os.path.abspath(os.path.expanduser( + "~/Library/Application Support/Cursor/User/globalStorage/state.vscdb" + )) + elif sys.platform == "linux" : # Linux 和其他类Unix系统 + self.db_path = os.path.abspath(os.path.expanduser( + "~/.config/Cursor/User/globalStorage/state.vscdb" + )) + else: + raise NotImplementedError(f"不支持的操作系统: {sys.platform}") + + def update_auth(self, email=None, access_token=None, refresh_token=None): + """ + 更新Cursor的认证信息 + :param email: 新的邮箱地址 + :param access_token: 新的访问令牌 + :param refresh_token: 新的刷新令牌 + :return: bool 是否成功更新 + """ + updates = [] + # 登录状态 + updates.append(("cursorAuth/cachedSignUpType", "Auth_0")) + + if email is not None: + updates.append(("cursorAuth/cachedEmail", email)) + if access_token is not None: + updates.append(("cursorAuth/accessToken", access_token)) + if refresh_token is not None: + updates.append(("cursorAuth/refreshToken", refresh_token)) + + if not updates: + print("没有提供任何要更新的值") + return False + + conn = None + try: + conn = sqlite3.connect(self.db_path) + cursor = conn.cursor() + + for key, value in updates: + + # 如果没有更新任何行,说明key不存在,执行插入 + # 检查 accessToken 是否存在 + check_query = f"SELECT COUNT(*) FROM itemTable WHERE key = ?" + cursor.execute(check_query, (key,)) + if cursor.fetchone()[0] == 0: + insert_query = "INSERT INTO itemTable (key, value) VALUES (?, ?)" + cursor.execute(insert_query, (key, value)) + else: + update_query = "UPDATE itemTable SET value = ? WHERE key = ?" + cursor.execute(update_query, (value, key)) + + if cursor.rowcount > 0: + print(f"成功更新 {key.split('/')[-1]}") + else: + print(f"未找到 {key.split('/')[-1]} 或值未变化") + + conn.commit() + return True + + except sqlite3.Error as e: + print("数据库错误:", str(e)) + return False + except Exception as e: + print("发生错误:", str(e)) + return False + finally: + if conn: + conn.close() diff --git a/cursor_pro_keep_alive.py b/cursor_pro_keep_alive.py new file mode 100644 index 0000000..fe8a6ae --- /dev/null +++ b/cursor_pro_keep_alive.py @@ -0,0 +1,790 @@ +import os +import platform +import json +import sys +from colorama import Fore, Style, init, Back +from enum import Enum +from typing import Optional +import subprocess + +from exit_cursor import ExitCursor +import patch_cursor_get_machine_id +from reset_machine import MachineIDResetter +from member_check import MemberChecker + +os.environ["PYTHONVERBOSE"] = "0" +os.environ["PYINSTALLER_VERBOSE"] = "0" + +import time +import random +from cursor_auth_manager import CursorAuthManager +import os +from logger import logging +from browser_utils import BrowserManager +from get_email_code import EmailVerificationHandler +from logo import print_logo +from config import Config +from datetime import datetime +from tqdm import tqdm + +# 定义 EMOJI 字典 +EMOJI = {"ERROR": "❌", "WARNING": "⚠️", "INFO": "ℹ️"} + + +class VerificationStatus(Enum): + """验证状态枚举""" + + PASSWORD_PAGE = "@name=password" + CAPTCHA_PAGE = "@data-index=0" + ACCOUNT_SETTINGS = "Account Settings" + + +class TurnstileError(Exception): + """Turnstile 验证相关异常""" + + pass + + +def save_screenshot(tab, stage: str, timestamp: bool = True) -> None: + """ + 保存页面截图 + + Args: + tab: 浏览器标签页对象 + stage: 截图阶段标识 + timestamp: 是否添加时间戳 + """ + try: + # 创建 screenshots 目录 + screenshot_dir = "screenshots" + if not os.path.exists(screenshot_dir): + os.makedirs(screenshot_dir) + + # 生成文件名 + if timestamp: + filename = f"turnstile_{stage}_{int(time.time())}.png" + else: + filename = f"turnstile_{stage}.png" + + filepath = os.path.join(screenshot_dir, filename) + + # 保存截图 + tab.get_screenshot(filepath) + logging.debug(f"截图已保存: {filepath}") + except Exception as e: + logging.warning(f"截图保存失败: {str(e)}") + + +def check_verification_success(tab) -> Optional[VerificationStatus]: + """ + 检查验证是否成功,增加超时等待 + """ + for status in VerificationStatus: + try: + if tab.ele(status.value, timeout=10): + logging.info(f"验证成功 - 已到达{status.name}页面") + return status + except Exception as e: + logging.debug(f"检查{status.name}状态时出错: {str(e)}") + return None + + +def handle_turnstile(tab, max_retries: int = 3, retry_interval: tuple = (2, 3)) -> bool: + """ + 处理 Turnstile 验证 + + Args: + tab: 浏览器标签页对象 + max_retries: 最大重试次数 + retry_interval: 重试间隔时间范围(最小值, 最大值) + + Returns: + bool: 验证是否成功 + """ + logging.info("正在检测 Turnstile 验证...") + save_screenshot(tab, "start") + + retry_count = 0 + + try: + while retry_count < max_retries: + retry_count += 1 + logging.debug(f"第 {retry_count} 次尝试验证") + + try: + # 定位验证框元素 + challenge_check = ( + tab.ele("@id=cf-turnstile", timeout=5) + .child() + .shadow_root.ele("tag:iframe") + .ele("tag:body") + .sr("tag:input") + ) + + if challenge_check: + logging.info("检测到 Turnstile 验证框,开始处理...") + # 随机延时后点击验证 + time.sleep(random.uniform(2, 4)) + challenge_check.click() + time.sleep(3) + + # 保存验证后的截图 + save_screenshot(tab, "clicked") + + # 检查验证结果 + if check_verification_success(tab): + logging.info("Turnstile 验证通过") + save_screenshot(tab, "success") + return True + + except Exception as e: + logging.debug(f"当前尝试未成功: {str(e)}") + + # 检查是否已经验证成功 + if check_verification_success(tab): + return True + + # 随机延时后继续下一次尝试 + time.sleep(random.uniform(*retry_interval)) + + # 超出最大重试次数 + logging.error(f"验证失败 - 已达到最大重试次数 {max_retries}") + save_screenshot(tab, "failed") + return False + + except Exception as e: + error_msg = f"Turnstile 验证过程发生异常: {str(e)}" + logging.error(error_msg) + save_screenshot(tab, "error") + raise TurnstileError(error_msg) + + +def get_cursor_session_token(tab, max_attempts=3, retry_interval=2): + """ + 获取Cursor会话token,带有重试机制 + :param tab: 浏览器标签页 + :param max_attempts: 最大尝试次数 + :param retry_interval: 重试间隔(秒) + :return: session token 或 None + """ + logging.info("开始获取cookie") + attempts = 0 + + while attempts < max_attempts: + try: + cookies = tab.cookies() + for cookie in cookies: + if cookie.get("name") == "WorkosCursorSessionToken": + return cookie["value"].split("%3A%3A")[1] + + attempts += 1 + if attempts < max_attempts: + logging.warning( + f"第 {attempts} 次尝试未获取到CursorSessionToken,{retry_interval}秒后重试..." + ) + time.sleep(retry_interval) + else: + logging.error( + f"已达到最大尝试次数({max_attempts}),获取CursorSessionToken失败" + ) + + except Exception as e: + logging.error(f"获取cookie失败: {str(e)}") + attempts += 1 + if attempts < max_attempts: + logging.info(f"将在 {retry_interval} 秒后重试...") + time.sleep(retry_interval) + + return None + + +def update_cursor_auth(email=None, access_token=None, refresh_token=None): + """ + 更新Cursor的认证信息的便捷函数 + """ + auth_manager = CursorAuthManager() + return auth_manager.update_auth(email, access_token, refresh_token) + + +def get_verification_code_with_retry(email_handler, max_retries=3): + """获取验证码的重试机制""" + for attempt in range(max_retries): + try: + print_status(f"第 {attempt + 1} 次尝试获取验证码...", "info") + code = email_handler.get_verification_code() + if code: + return code + time.sleep(5) # 等待5秒后重试 + except Exception as e: + print_status(f"获取验证码出错: {str(e)}", "error") + if attempt < max_retries - 1: + print_status("等待重试...", "info") + time.sleep(5) + return None + + +def handle_verification_code(tab, email_handler): + """处理验证码输入流程""" + max_retries = 3 + retry_count = 0 + + while retry_count < max_retries: + try: + if tab.ele("Account Settings", timeout=3): + print_status("注册成功 - 已进入账户设置页面", "success") + return True + + if tab.ele("@data-index=0", timeout=3): + print_status("正在获取邮箱验证码...") + code = get_verification_code_with_retry(email_handler) + if not code: + print_status("获取验证码失败", "error") + return False + + print_status(f"成功获取验证码: {code}", "success") + print_status("正在输入验证码...") + + # 快速输入验证码 + for i, digit in enumerate(code): + tab.ele(f"@data-index={i}").input(digit) + time.sleep(0.1) + + print_status("验证码输入完成", "success") + time.sleep(1) + return True + + retry_count += 1 + time.sleep(1) + + except Exception as e: + print_status(f"验证码处理过程出错: {str(e)}", "error") + retry_count += 1 + if retry_count < max_retries: + time.sleep(1) + continue + return False + + return False + + +def sign_up_account(browser, tab): + print_step_header("账号注册流程") + print_status(f"正在访问注册页面: {sign_up_url}") + tab.get(sign_up_url) + + try: + if tab.ele("@name=first_name", timeout=5): + print_status("正在填写个人信息...") + + # 快速填写表单 + tab.ele("@name=first_name").input(first_name) + tab.ele("@name=last_name").input(last_name) + tab.ele("@name=email").input(account) + tab.ele("@type=submit").click() + print_status("个人信息已提交", "success") + + except Exception as e: + print_status(f"注册页面访问失败: {str(e)}", "error") + return False + + if not handle_turnstile(tab): + print_status("Turnstile 验证失败", "error") + return False + + try: + if tab.ele("@name=password", timeout=5): + print_status("正在设置密码...") + tab.ele("@name=password").input(password) + tab.ele("@type=submit").click() + print_status("密码设置完成", "success") + else: + print_status("密码输入页面加载超时", "error") + return False + + except Exception as e: + print_status(f"密码设置失败: {str(e)}", "error") + return False + + if tab.ele("This email is not available.", timeout=2): + print_status("注册失败:邮箱已被使用", "error") + return False + + if not handle_turnstile(tab): + print_status("第二次 Turnstile 验证失败", "error") + return False + + if not handle_verification_code(tab, email_handler): + print_status("验证码处理失败", "error") + return False + + if not handle_turnstile(tab): + print_status("最后一次 Turnstile 验证失败", "error") + return False + + print_progress("系统处理中", 0.02) + + print_status("正在获取账户信息...") + tab.get(settings_url) + + print_step_header("注册完成") + print(f"\n{Fore.GREEN}Cursor 账号信息:{Style.RESET_ALL}") + print(f"{Fore.WHITE}├─ 邮箱: {account}") + print(f"└─ 密码: {password}{Style.RESET_ALL}") + + time.sleep(2) + return True + + +class EmailGenerator: + def __init__( + self, + password="".join( + random.choices( + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*", + k=12, + ) + ), + ): + configInstance = Config() + configInstance.print_config() + self.domain = configInstance.get_domain() + self.default_password = password + self.default_first_name = self.generate_random_name() + self.default_last_name = self.generate_random_name() + + def generate_random_name(self, length=6): + """生成随机用户名""" + first_letter = random.choice("ABCDEFGHIJKLMNOPQRSTUVWXYZ") + rest_letters = "".join( + random.choices("abcdefghijklmnopqrstuvwxyz", k=length - 1) + ) + return first_letter + rest_letters + + def generate_email(self, length=8): + """生成随机邮箱地址""" + random_str = "".join(random.choices("abcdefghijklmnopqrstuvwxyz", k=length)) + timestamp = str(int(time.time()))[-6:] # 使用时间戳后6位 + return f"{random_str}{timestamp}@{self.domain}" + + def get_account_info(self): + """获取完整的账号信息""" + return { + "email": self.generate_email(), + "password": self.default_password, + "first_name": self.default_first_name, + "last_name": self.default_last_name, + } + + +def get_user_agent(browser=None): + """获取user_agent + Args: + browser: 已存在的浏览器实例,如果提供则使用该实例 + """ + try: + if browser: + return browser.latest_tab.run_js("return navigator.userAgent") + else: + # 如果没有提供浏览器实例,则创建新的 + browser_manager = BrowserManager() + browser = browser_manager.init_browser() + user_agent = browser.latest_tab.run_js("return navigator.userAgent") + browser_manager.quit() # 仅在新创建的实例时关闭 + return user_agent + except Exception as e: + print_status(f"获取user agent失败: {str(e)}", "warning") + return "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + + +def check_cursor_version(): + """检查cursor版本""" + pkg_path, main_path = patch_cursor_get_machine_id.get_cursor_paths() + with open(pkg_path, "r", encoding="utf-8") as f: + version = json.load(f)["version"] + return patch_cursor_get_machine_id.version_check(version, min_version="0.45.0") + + +def reset_machine_id(greater_than_0_45): + """执行深度重置,包括设备ID和系统MachineGuid""" + try: + print_status("开始执行深度重置...", "info") + + # 获取路径信息 + storage_path = os.path.expandvars(r"%APPDATA%\Cursor\User\globalStorage\storage.json") + backup_path = os.path.expandvars(r"%APPDATA%\Cursor\User\globalStorage\backups") + + # 检查并创建备份目录 + if not os.path.exists(backup_path): + os.makedirs(backup_path) + + # 准备 PowerShell 命令 + cmd = f''' + # 设置输出编码为 UTF-8 + [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + $OutputEncoding = [System.Text.Encoding]::UTF8 + + # 配置文件路径 + $STORAGE_FILE = "{storage_path}" + $BACKUP_DIR = "{backup_path}" + + # 备份现有配置 + if (Test-Path $STORAGE_FILE) {{ + $backupName = "storage.json.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')" + Copy-Item $STORAGE_FILE "$BACKUP_DIR\\$backupName" + }} + + # 生成新的 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 + }} + + $MAC_MACHINE_ID = New-StandardMachineId + $UUID = [System.Guid]::NewGuid().ToString() + $prefixBytes = [System.Text.Encoding]::UTF8.GetBytes("auth0|user_") + $prefixHex = -join ($prefixBytes | ForEach-Object {{ [System.Convert]::ToString($_, 16).PadLeft(2, '0') }}) + $randomPart = -join (1..32 | ForEach-Object {{ [Convert]::ToString((Get-Random -Minimum 0 -Maximum 256), 16).PadLeft(2, '0') }}) + $MACHINE_ID = "$prefixHex$randomPart" + $SQM_ID = "{{$([System.Guid]::NewGuid().ToString().ToUpper())}}" + + # 更新配置文件 + $originalContent = Get-Content $STORAGE_FILE -Raw -Encoding UTF8 + $config = $originalContent | ConvertFrom-Json + + # 更新特定的值 + $config.'telemetry.machineId' = $MACHINE_ID + $config.'telemetry.macMachineId' = $MAC_MACHINE_ID + $config.'telemetry.devDeviceId' = $UUID + $config.'telemetry.sqmId' = $SQM_ID + + # 保存更新后的配置 + $updatedJson = $config | ConvertTo-Json -Depth 10 + [System.IO.File]::WriteAllText($STORAGE_FILE, $updatedJson, [System.Text.Encoding]::UTF8) + + # 更新系统 MachineGuid + $newMachineGuid = [System.Guid]::NewGuid().ToString() + $registryPath = "HKLM:\\SOFTWARE\\Microsoft\\Cryptography" + + # 备份原始值 + $originalGuid = (Get-ItemProperty -Path $registryPath -Name "MachineGuid").MachineGuid + $backupFile = "$BACKUP_DIR\\MachineGuid.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')" + $originalGuid | Out-File $backupFile -Encoding UTF8 + + # 更新注册表 + Set-ItemProperty -Path $registryPath -Name "MachineGuid" -Value $newMachineGuid + ''' + + # 执行 PowerShell 命令 + process = subprocess.run( + ["powershell", "-NoProfile", "-NonInteractive", "-Command", cmd], + capture_output=True, + text=True, + encoding='utf-8' + ) + + if process.returncode == 0: + print_status("深度重置完成", "success") + if greater_than_0_45: + patch_cursor_get_machine_id.patch_cursor_get_machine_id() + else: + MachineIDResetter().reset_machine_ids() + return True + else: + print_status("深度重置失败", "error") + return False + + except Exception as e: + print_status(f"深度重置出错: {str(e)}", "error") + return False + + +def print_menu(): + """打印美化的菜单""" + print(f"\n{Fore.CYAN}{'='*60}") + print(f"{Fore.WHITE} 系统功能选项菜单") + print(f"{Fore.CYAN}{'='*60}\n") + + print(f"{Fore.YELLOW}[1]{Fore.WHITE} 仅重置机器码") + print(f" {Fore.LIGHTBLACK_EX}└─ 快速重置机器码,保持其他设置不变{Style.RESET_ALL}") + + print(f"\n{Fore.YELLOW}[2]{Fore.WHITE} 一键无痕重置Fast") + print(f" {Fore.LIGHTBLACK_EX}└─ 执行完整的无痕重置使用量{Style.RESET_ALL}") + + print(f"\n{Fore.YELLOW}[3]{Fore.WHITE} 深度顽固设备清理") + print(f" {Fore.LIGHTBLACK_EX}└─ 强制清理所有设备标识和系统指纹{Style.RESET_ALL}") + + print(f"\n{Fore.CYAN}{'='*60}{Style.RESET_ALL}") + + +def print_progress(message, delay=0.03): + """打印带进度条的提示信息""" + for i in tqdm(range(100), + desc=f"{Fore.CYAN}{message}", + bar_format="{desc}: {percentage:3.0f}%|{bar}| {n_fmt}/{total_fmt}", + ncols=70): + time.sleep(delay) + print(Style.RESET_ALL) + + +def get_user_input(prompt, valid_options=None): + """获取用户输入的美化版本""" + while True: + print(f"\n{Fore.YELLOW}>>>{Fore.WHITE} {prompt}{Style.RESET_ALL}", end=" ") + user_input = input().strip() + + if valid_options and user_input not in valid_options: + print(f"{Fore.RED}✗ 无效的输入,请重试{Style.RESET_ALL}") + continue + + return user_input + + +def load_last_account(): + """加载上次使用的账号""" + try: + if os.path.exists("last_account.json"): + with open("last_account.json", "r", encoding="utf-8") as f: + data = json.load(f) + return data.get("email"), data.get("last_used") + except Exception: + pass + return None, None + + +def save_last_account(email): + """保存最后使用的账号""" + try: + data = { + "email": email, + "last_used": datetime.now().strftime("%Y-%m-%d %H:%M:%S") + } + with open("last_account.json", "w", encoding="utf-8") as f: + json.dump(data, f, ensure_ascii=False, indent=2) + except Exception: + pass + + +def verify_member(): + """验证会员身份""" + checker = MemberChecker() + + while True: + print(f"\n{Fore.CYAN}{'='*30} 会员验证 {'='*30}{Style.RESET_ALL}") + + # 加载上次使用的账号 + last_email, last_used = load_last_account() + if last_email: + print(f"\n{Fore.CYAN}[提示] 上次使用账号: {last_email}") + print(f" 最后使用时间: {last_used}{Style.RESET_ALL}") + print(f"\n{Fore.YELLOW}直接按回车使用上次账号,或输入新的邮箱/订单号{Style.RESET_ALL}") + + keyword = get_user_input("请输入会员邮箱或订单号:") + + # 如果直接回车且有上次账号记录,使用上次的账号 + if not keyword and last_email: + keyword = last_email + print(f"{Fore.CYAN}[信息] 使用上次账号: {keyword}{Style.RESET_ALL}") + elif not keyword: + print(f"{Fore.RED}✗ 输入不能为空,请重试{Style.RESET_ALL}") + continue + + animate_loading("正在验证会员信息", 2) # 使用动画加载替代进度条 + result = checker.check_member(keyword) + + if result['is_valid']: + # 保存成功验证的账号 + save_last_account(keyword) + + print(f"\n{Fore.GREEN}✓ 会员验证通过!{Style.RESET_ALL}") + print(f"\n{Fore.CYAN}会员详细信息:{Style.RESET_ALL}") + print(f"{Fore.WHITE}├─ 邮箱: {result['email']}") + print(f"├─ 订单号: {result['order_id']}") + print(f"├─ 到期时间: {result['expire_time']}") + print(f"├─ 使用限制: {result['usage_limit']}") + print(f"└─ 已使用次数: {result['used_count']}{Style.RESET_ALL}") + + if result['expire_time']: + expire_time = datetime.strptime(result['expire_time'], "%Y-%m-%d %H:%M:%S") + if expire_time < datetime.now(): + print(f"\n{Fore.RED}✗ 会员已过期,请续费后重试{Style.RESET_ALL}") + sys.exit(1) + return result + + print(f"\n{Fore.RED}✗ 验证失败: {result['msg']}{Style.RESET_ALL}") + retry = get_user_input("是否重试? (y/n):", ['y','n']) + if retry != 'y': + sys.exit(1) + + +def print_status(message, status="info"): + """打印状态信息""" + prefix = { + "info": f"{Fore.CYAN}[信息]{Style.RESET_ALL}", + "success": f"{Fore.GREEN}[成功]{Style.RESET_ALL}", + "error": f"{Fore.RED}[错误]{Style.RESET_ALL}", + "warning": f"{Fore.YELLOW}[警告]{Style.RESET_ALL}" + } + print(f"\n{prefix.get(status, prefix['info'])} {message}") + + +def animate_loading(message, duration=1): + """显示加载动画""" + chars = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏" + start_time = time.time() + i = 0 + while time.time() - start_time < duration: + print(f"\r{Fore.CYAN}{chars[i]}{Style.RESET_ALL} {message}", end="") + time.sleep(0.1) + i = (i + 1) % len(chars) + print() + + +def print_step_header(step_name): + """打印步骤标题""" + print(f"\n{Fore.CYAN}{'='*20} {step_name} {'='*20}{Style.RESET_ALL}") + + +if __name__ == "__main__": + try: + init() # 初始化colorama + print_logo() + + print_step_header("系统初始化") + + # 会员验证 + member_info = verify_member() + print_progress("初始化程序", 0.02) + + print_menu() + choice = get_user_input("请选择操作选项:", ["1", "2", "3"]) + + if choice == "1": + print_step_header("重置机器码") + print_progress("正在重置机器码", 0.02) + + # 先关闭 Cursor 进程 + print_status("正在关闭 Cursor...", "info") + try: + subprocess.run(["taskkill", "/F", "/IM", "Cursor.exe"], capture_output=True) + print_status("Cursor 进程已终止", "success") + except Exception as e: + print_status(f"关闭 Cursor 进程失败: {str(e)}", "warning") + + # 等待进程完全关闭 + time.sleep(2) + + greater_than_0_45 = check_cursor_version() + if reset_machine_id(greater_than_0_45): + print_status("机器码重置完成", "success") + print_status("请重新启动 Cursor", "info") + else: + print_status("机器码重置失败", "error") + sys.exit(0) + elif choice == "3": + print_step_header("深度顽固设备清理") + print_status("正在启动深度清理程序...", "info") + + # 先关闭 Cursor + try: + subprocess.run(["taskkill", "/F", "/IM", "Cursor.exe"], capture_output=True) + print_status("Cursor 进程已终止", "success") + except Exception as e: + print_status(f"关闭 Cursor 进程失败: {str(e)}", "warning") + + time.sleep(2) + + # 以管理员权限运行 full_reset.py + try: + subprocess.run([ + "powershell", + "-Command", + "Start-Process python -ArgumentList 'full_reset.py' -Verb RunAs -Wait" + ]) + print_status("深度清理完成", "success") + except Exception as e: + print_status(f"深度清理失败: {str(e)}", "error") + sys.exit(0) + + print_step_header("浏览器初始化") + animate_loading("正在启动浏览器内核", 2) + + # 先初始化浏览器 + browser_manager = BrowserManager() + browser = browser_manager.init_browser() + + animate_loading("获取系统指纹", 1) + # 使用已初始化的浏览器获取 user_agent + user_agent = get_user_agent(browser) + if "HeadlessChrome" in user_agent: + user_agent = user_agent.replace("HeadlessChrome", "Chrome") + print_status("已优化浏览器指纹", "success") + + # 使用优化后的 user_agent 重新初始化浏览器 + browser_manager.quit() + browser_manager = BrowserManager() + browser = browser_manager.init_browser(user_agent) + + print_status("浏览器初始化完成", "success") + + print_status("正在初始化邮箱验证模块...") + email_handler = EmailVerificationHandler() + + print_step_header("配置信息") + login_url = "https://authenticator.cursor.sh" + sign_up_url = "https://authenticator.cursor.sh/sign-up" + settings_url = "https://www.cursor.com/settings" + mail_url = "https://tempmail.plus" + + print_status("正在生成随机账号信息...") + email_generator = EmailGenerator() + account = email_generator.generate_email() + password = email_generator.default_password + first_name = email_generator.default_first_name + last_name = email_generator.default_last_name + + print_status(f"生成的邮箱账号: {account}") + auto_update_cursor_auth = True + + tab = browser.latest_tab + + tab.run_js("try { turnstile.reset() } catch(e) { }") + + if sign_up_account(browser, tab): + print_status("正在获取会话令牌...") + token = get_cursor_session_token(tab) + if token: + print_status("更新认证信息...") + update_cursor_auth( + email=account, access_token=token, refresh_token=token + ) + + print_status("执行完整重置...") + # 关闭浏览器以释放文件锁 + if browser_manager: + browser_manager.quit() + + # 执行完整重置 + from full_reset import full_system_reset + if full_system_reset(): + print_status("完整重置成功", "success") + print_status("所有操作已完成", "success") + print(f"\n{Fore.GREEN}✓ 重置完成!下次启动 Cursor 将使用新的配置。{Style.RESET_ALL}") + else: + print_status("完整重置失败", "error") + else: + print_status("获取会话令牌失败,注册流程未完成", "error") + + except Exception as e: + print_status(f"程序执行出现错误: {str(e)}", "error") + import traceback + print_status(traceback.format_exc(), "error") + finally: + if browser_manager: + browser_manager.quit() + input(f"\n{Fore.YELLOW}按回车键退出...{Style.RESET_ALL}") diff --git a/deep_reset.py b/deep_reset.py new file mode 100644 index 0000000..1e43d5b --- /dev/null +++ b/deep_reset.py @@ -0,0 +1,114 @@ +import os +import subprocess +import logging +from colorama import Fore, Style + +def print_status(message, status="info"): + """打印状态信息""" + prefix = { + "info": f"{Fore.CYAN}[信息]{Style.RESET_ALL}", + "success": f"{Fore.GREEN}[成功]{Style.RESET_ALL}", + "error": f"{Fore.RED}[错误]{Style.RESET_ALL}", + "warning": f"{Fore.YELLOW}[警告]{Style.RESET_ALL}" + } + print(f"\n{prefix.get(status, prefix['info'])} {message}") + +def deep_reset(): + """执行深度重置,包括设备ID和系统MachineGuid""" + try: + print_status("开始执行深度重置...", "info") + + # 获取路径信息 + storage_path = os.path.expandvars(r"%APPDATA%\Cursor\User\globalStorage\storage.json") + backup_path = os.path.expandvars(r"%APPDATA%\Cursor\User\globalStorage\backups") + + # 检查并创建备份目录 + if not os.path.exists(backup_path): + os.makedirs(backup_path) + + # 准备 PowerShell 命令 + cmd = f''' + # 设置输出编码为 UTF-8 + [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + $OutputEncoding = [System.Text.Encoding]::UTF8 + + # 配置文件路径 + $STORAGE_FILE = "{storage_path}" + $BACKUP_DIR = "{backup_path}" + + # 备份现有配置 + if (Test-Path $STORAGE_FILE) {{ + $backupName = "storage.json.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')" + Copy-Item $STORAGE_FILE "$BACKUP_DIR\\$backupName" + }} + + # 生成新的 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 + }} + + $MAC_MACHINE_ID = New-StandardMachineId + $UUID = [System.Guid]::NewGuid().ToString() + $prefixBytes = [System.Text.Encoding]::UTF8.GetBytes("auth0|user_") + $prefixHex = -join ($prefixBytes | ForEach-Object {{ [System.Convert]::ToString($_, 16).PadLeft(2, '0') }}) + $randomPart = -join (1..32 | ForEach-Object {{ [Convert]::ToString((Get-Random -Minimum 0 -Maximum 256), 16).PadLeft(2, '0') }}) + $MACHINE_ID = "$prefixHex$randomPart" + $SQM_ID = "{{$([System.Guid]::NewGuid().ToString().ToUpper())}}" + + # 更新配置文件 + $originalContent = Get-Content $STORAGE_FILE -Raw -Encoding UTF8 + $config = $originalContent | ConvertFrom-Json + + # 更新特定的值 + $config.'telemetry.machineId' = $MACHINE_ID + $config.'telemetry.macMachineId' = $MAC_MACHINE_ID + $config.'telemetry.devDeviceId' = $UUID + $config.'telemetry.sqmId' = $SQM_ID + + # 保存更新后的配置 + $updatedJson = $config | ConvertTo-Json -Depth 10 + [System.IO.File]::WriteAllText($STORAGE_FILE, $updatedJson, [System.Text.Encoding]::UTF8) + + # 更新系统 MachineGuid + $newMachineGuid = [System.Guid]::NewGuid().ToString() + $registryPath = "HKLM:\\SOFTWARE\\Microsoft\\Cryptography" + + # 备份原始值 + $originalGuid = (Get-ItemProperty -Path $registryPath -Name "MachineGuid").MachineGuid + $backupFile = "$BACKUP_DIR\\MachineGuid.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')" + $originalGuid | Out-File $backupFile -Encoding UTF8 + + # 更新注册表 + Set-ItemProperty -Path $registryPath -Name "MachineGuid" -Value $newMachineGuid + ''' + + # 执行 PowerShell 命令 + process = subprocess.run( + ["powershell", "-NoProfile", "-NonInteractive", "-Command", cmd], + capture_output=True, + text=True, + encoding='utf-8' + ) + + if process.returncode == 0: + print_status("深度重置完成", "success") + return True + else: + print_status("深度重置失败", "error") + if process.stderr: + print_status(f"错误信息: {process.stderr}", "error") + return False + + except Exception as e: + print_status(f"深度重置出错: {str(e)}", "error") + return False + +if __name__ == "__main__": + deep_reset() \ No newline at end of file diff --git a/exit_cursor.py b/exit_cursor.py new file mode 100644 index 0000000..cd5f576 --- /dev/null +++ b/exit_cursor.py @@ -0,0 +1,68 @@ +import psutil +from logger import logging +import time + +def ExitCursor(timeout=5): + """ + 温和地关闭 Cursor 进程 + + Args: + timeout (int): 等待进程自然终止的超时时间(秒) + Returns: + bool: 是否成功关闭所有进程 + """ + try: + logging.info("开始退出Cursor...") + cursor_processes = [] + # 收集所有 Cursor 进程 + for proc in psutil.process_iter(['pid', 'name']): + try: + if proc.info['name'].lower() in ['cursor.exe', 'cursor']: + cursor_processes.append(proc) + except (psutil.NoSuchProcess, psutil.AccessDenied): + continue + + if not cursor_processes: + logging.info("未发现运行中的 Cursor 进程") + return True + + # 温和地请求进程终止 + for proc in cursor_processes: + try: + if proc.is_running(): + proc.terminate() # 发送终止信号 + except (psutil.NoSuchProcess, psutil.AccessDenied): + continue + + # 等待进程自然终止 + start_time = time.time() + while time.time() - start_time < timeout: + still_running = [] + for proc in cursor_processes: + try: + if proc.is_running(): + still_running.append(proc) + except (psutil.NoSuchProcess, psutil.AccessDenied): + continue + + if not still_running: + logging.info("所有 Cursor 进程已正常关闭") + return True + + # 等待一小段时间再检查 + time.sleep(0.5) + + # 如果超时后仍有进程在运行 + if still_running: + process_list = ", ".join([str(p.pid) for p in still_running]) + logging.warning(f"以下进程未能在规定时间内关闭: {process_list}") + return False + + return True + + except Exception as e: + logging.error(f"关闭 Cursor 进程时发生错误: {str(e)}") + return False + +if __name__ == "__main__": + ExitCursor() diff --git a/full_reset.py b/full_reset.py new file mode 100644 index 0000000..dbc7445 --- /dev/null +++ b/full_reset.py @@ -0,0 +1,312 @@ +import os +import ctypes +import subprocess +from colorama import Fore, Style +import sys +import logging +import codecs +import traceback +import time +import random + +# 设置日志记录 +try: + sys.stdout = codecs.getwriter('utf-8')(sys.stdout.buffer, 'strict') + sys.stderr = codecs.getwriter('utf-8')(sys.stderr.buffer, 'strict') +except Exception as e: + print(f"编码设置失败: {e}") + +# 确保日志目录存在 +log_dir = 'logs' +os.makedirs(log_dir, exist_ok=True) +log_file = os.path.join(log_dir, 'full_reset_debug.log') + +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler(log_file, encoding='utf-8', mode='w'), + logging.StreamHandler(sys.stdout) + ] +) + +def pause_exit(status=0): + """暂停并等待用户输入后退出""" + print("\n" + "="*50) + print("按回车键退出...") + input() + sys.exit(status) + +def is_admin(): + try: + logging.debug("检查管理员权限") + result = ctypes.windll.shell32.IsUserAnAdmin() + logging.debug(f"管理员权限检查结果: {result}") + return result + except Exception as e: + logging.error(f"检查管理员权限时出错: {str(e)}") + logging.error(traceback.format_exc()) + return False + +def run_powershell_reset(): + """运行 PowerShell 重置脚本""" + try: + logging.debug("开始准备 PowerShell 重置") + + # 获取并记录路径信息 + storage_path = os.path.expandvars(r"%APPDATA%\Cursor\User\globalStorage\storage.json") + backup_path = os.path.expandvars(r"%APPDATA%\Cursor\User\globalStorage\backups") + + logging.info(f"存储文件路径: {storage_path}") + logging.info(f"备份目录路径: {backup_path}") + + # 检查文件和目录是否存在 + if os.path.exists(storage_path): + logging.info("存储文件存在") + if not os.access(storage_path, os.W_OK): + logging.error("存储文件没有写入权限") + print(f"{Fore.RED}错误: 没有文件写入权限{Style.RESET_ALL}") + return False + else: + logging.error("存储文件不存在") + print(f"{Fore.RED}错误: 存储文件不存在{Style.RESET_ALL}") + return False + + if not os.path.exists(backup_path): + try: + os.makedirs(backup_path) + logging.info("已创建备份目录") + except Exception as e: + logging.error(f"创建备份目录失败: {e}") + print(f"{Fore.RED}错误: 无法创建备份目录{Style.RESET_ALL}") + return False + + # 准备 PowerShell 命令 + cmd = f''' + # 设置输出编码为 UTF-8 + [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + $OutputEncoding = [System.Text.Encoding]::UTF8 + chcp 65001 | Out-Null + + # 配置文件路径 + $STORAGE_FILE = "{storage_path}" + $BACKUP_DIR = "{backup_path}" + + Write-Host "`e[32m[信息]`e[0m 检查 Cursor 进程..." + + # 检查管理员权限 + 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 "`e[31m[错误]`e[0m 请以管理员身份运行此脚本" + exit 1 + }} + + # 创建备份目录 + if (-not (Test-Path $BACKUP_DIR)) {{ + New-Item -ItemType Directory -Path $BACKUP_DIR | Out-Null + }} + + # 备份现有配置 + if (Test-Path $STORAGE_FILE) {{ + Write-Host "`e[32m[信息]`e[0m 正在备份配置文件..." + $backupName = "storage.json.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')" + Copy-Item $STORAGE_FILE "$BACKUP_DIR\\$backupName" + }} + + # 生成新的 ID + Write-Host "`e[32m[信息]`e[0m 正在生成新的 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 + }} + + $MAC_MACHINE_ID = New-StandardMachineId + $UUID = [System.Guid]::NewGuid().ToString() + $prefixBytes = [System.Text.Encoding]::UTF8.GetBytes("auth0|user_") + $prefixHex = -join ($prefixBytes | ForEach-Object {{ [System.Convert]::ToString($_, 16).PadLeft(2, '0') }}) + $randomPart = -join (1..32 | ForEach-Object {{ [Convert]::ToString((Get-Random -Minimum 0 -Maximum 256), 16).PadLeft(2, '0') }}) + $MACHINE_ID = "$prefixHex$randomPart" + $SQM_ID = "{{$([System.Guid]::NewGuid().ToString().ToUpper())}}" + + # 更新配置文件 + Write-Host "`e[32m[信息]`e[0m 正在更新配置..." + + try {{ + if (-not (Test-Path $STORAGE_FILE)) {{ + Write-Host "`e[31m[错误]`e[0m 未找到配置文件: $STORAGE_FILE" + exit 1 + }} + + $originalContent = Get-Content $STORAGE_FILE -Raw -Encoding UTF8 + $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 "`e[32m[信息]`e[0m 成功更新配置文件" + + # 更新系统 MachineGuid + try {{ + $newMachineGuid = [System.Guid]::NewGuid().ToString() + $registryPath = "HKLM:\\SOFTWARE\\Microsoft\\Cryptography" + + # 备份原始值 + $originalGuid = (Get-ItemProperty -Path $registryPath -Name "MachineGuid").MachineGuid + $backupFile = "$BACKUP_DIR\\MachineGuid.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')" + $originalGuid | Out-File $backupFile -Encoding UTF8 + + # 更新注册表 + Set-ItemProperty -Path $registryPath -Name "MachineGuid" -Value $newMachineGuid + Write-Host "`e[32m[信息]`e[0m 已更新系统 MachineGuid: $newMachineGuid" + Write-Host "`e[32m[信息]`e[0m 原始值已备份至: $backupFile" + }} + catch {{ + Write-Host "`e[31m[错误]`e[0m 更新系统 MachineGuid 失败: $_" + }} + + # 显示结果 + Write-Host "" + Write-Host "`e[32m[信息]`e[0m 已更新配置:" + Write-Host "`e[34m[调试]`e[0m machineId: $MACHINE_ID" + Write-Host "`e[34m[调试]`e[0m macMachineId: $MAC_MACHINE_ID" + Write-Host "`e[34m[调试]`e[0m devDeviceId: $UUID" + Write-Host "`e[34m[调试]`e[0m sqmId: $SQM_ID" + + }} catch {{ + Write-Host "`e[31m[错误]`e[0m 主要操作失败: $_" + Write-Host "`e[33m[尝试]`e[0m 使用备选方法..." + + try {{ + $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 "`e[32m[信息]`e[0m 使用备选方法成功写入配置" + }} catch {{ + Write-Host "`e[31m[错误]`e[0m 所有尝试都失败了" + Write-Host "错误详情: $_" + Write-Host "目标文件: $STORAGE_FILE" + Write-Host "请确保您有足够的权限访问该文件" + exit 1 + }} + }} + + exit 0 + ''' + + logging.info("准备执行 PowerShell 命令") + + # 执行命令并捕获输出 + process = subprocess.run( + ["powershell", "-NoProfile", "-NonInteractive", "-Command", cmd], + capture_output=True, + text=True, + encoding='utf-8' + ) + + # 记录命令输出 + if process.stdout: + logging.info("PowerShell 输出:") + for line in process.stdout.splitlines(): + logging.info(f"PS> {line}") + + if process.stderr: + logging.error("PowerShell 错误:") + for line in process.stderr.splitlines(): + logging.error(f"PS Error> {line}") + + if process.returncode != 0: + logging.error(f"PowerShell 命令执行失败,返回码: {process.returncode}") + return False + + logging.info("PowerShell 命令执行成功") + return True + + except subprocess.CalledProcessError as e: + logging.error(f"PowerShell 脚本执行失败: {e}") + logging.error(f"返回码: {e.returncode}") + if hasattr(e, 'stdout') and e.stdout: + logging.error(f"标准输出: {e.stdout}") + if hasattr(e, 'stderr') and e.stderr: + logging.error(f"标准错误: {e.stderr}") + logging.error(traceback.format_exc()) + return False + except Exception as e: + logging.error(f"执行过程中出错: {str(e)}") + logging.error(traceback.format_exc()) + return False + +def full_system_reset(): + """执行完整的系统重置""" + try: + # 检查管理员权限 + if not is_admin(): + logging.error("需要管理员权限") + return False + + # 运行重置 + logging.info("开始执行完整重置...") + if run_powershell_reset(): + logging.info("重置完成") + return True + else: + logging.error("重置失败") + return False + + except Exception as e: + logging.error(f"系统重置出错: {str(e)}") + logging.error(traceback.format_exc()) + return False + +if __name__ == "__main__": + try: + # 检查管理员权限 + if not is_admin(): + print(f"{Fore.RED}[错误] 请以管理员身份运行此程序{Style.RESET_ALL}") + print(f"{Fore.YELLOW}[提示] 请右键点击程序,选择'以管理员身份运行'{Style.RESET_ALL}") + pause_exit(1) + + # 运行重置 + print(f"{Fore.CYAN}[信息] 开始执行完整重置...{Style.RESET_ALL}") + if full_system_reset(): + print(f"{Fore.GREEN}[成功] 重置完成!{Style.RESET_ALL}") + print(f"{Fore.YELLOW}[提示] 请重启 Cursor 以应用新的配置{Style.RESET_ALL}") + else: + print(f"{Fore.RED}[错误] 重置失败{Style.RESET_ALL}") + + except Exception as e: + logging.error(f"程序执行出错: {str(e)}") + logging.error(traceback.format_exc()) + print(f"{Fore.RED}[错误] 程序执行出现异常: {str(e)}{Style.RESET_ALL}") + finally: + pause_exit() \ No newline at end of file diff --git a/get_email_code copy.py b/get_email_code copy.py new file mode 100644 index 0000000..f83cc82 --- /dev/null +++ b/get_email_code copy.py @@ -0,0 +1,220 @@ +import logging +import time +import re +from config import Config +import requests +import email +import imaplib +import json + + +class EmailVerificationHandler: + def __init__(self): + self.imap = Config().get_imap() + self.username = Config().get_temp_mail() + self.epin = Config().get_temp_mail_epin() + self.session = requests.Session() + self.emailExtension = Config().get_temp_mail_ext() + + def get_verification_code(self): + """获取验证码,带重试机制""" + max_retries = 5 + retry_delay = 3 + code = None + + for attempt in range(max_retries): + try: + print(f"\n第 {attempt + 1} 次尝试获取验证码...") + + if self.imap is False: + # 等待并获取最新邮件 + code, first_id = self._get_latest_mail_code() + if code: + print(f"成功获取验证码: {code}") + return code + elif first_id: # 有邮件但没有验证码 + self._cleanup_mail(first_id) + else: + code = self._get_mail_code_by_imap() + if code: + print(f"成功获取验证码: {code}") + return code + + if attempt < max_retries - 1: + print(f"等待 {retry_delay} 秒后重试...") + time.sleep(retry_delay) + + except Exception as e: + print(f"获取验证码时出错: {str(e)}") + if attempt < max_retries - 1: + print(f"等待 {retry_delay} 秒后重试...") + time.sleep(retry_delay) + + print("获取验证码失败,已达到最大重试次数") + return None + + # 使用imap获取邮件 + def _get_mail_code_by_imap(self, retry = 0): + if retry > 0: + time.sleep(3) + if retry >= 20: + raise Exception("获取验证码超时") + try: + # 连接到IMAP服务器 + mail = imaplib.IMAP4_SSL(self.imap['imap_server'], self.imap['imap_port']) + mail.login(self.imap['imap_user'], self.imap['imap_pass']) + mail.select(self.imap['imap_dir']) + + status, messages = mail.search(None, 'FROM', '"no-reply@cursor.sh"') + if status != 'OK': + return None + + mail_ids = messages[0].split() + if not mail_ids: + # 没有获取到,就在获取一次 + return self._get_mail_code_by_imap(retry=retry + 1) + + latest_mail_id = mail_ids[-1] + + # 获取邮件内容 + status, msg_data = mail.fetch(latest_mail_id, '(RFC822)') + if status != 'OK': + return None + + raw_email = msg_data[0][1] + email_message = email.message_from_bytes(raw_email) + + # 提取邮件正文 + body = self._extract_imap_body(email_message) + if body: + # 使用正则表达式查找6位数字验证码 + code_match = re.search(r"\b\d{6}\b", body) + if code_match: + code = code_match.group() + # 删除邮件 + mail.store(latest_mail_id, '+FLAGS', '\\Deleted') + mail.expunge() + mail.logout() + # print(f"找到的验证码: {code}") + return code + # print("未找到验证码") + mail.logout() + return None + except Exception as e: + print(f"发生错误: {e}") + return None + + def _extract_imap_body(self, email_message): + # 提取邮件正文 + if email_message.is_multipart(): + for part in email_message.walk(): + content_type = part.get_content_type() + content_disposition = str(part.get("Content-Disposition")) + if content_type == "text/plain" and "attachment" not in content_disposition: + charset = part.get_content_charset() or 'utf-8' + try: + body = part.get_payload(decode=True).decode(charset, errors='ignore') + return body + except Exception as e: + logging.error(f"解码邮件正文失败: {e}") + else: + content_type = email_message.get_content_type() + if content_type == "text/plain": + charset = email_message.get_content_charset() or 'utf-8' + try: + body = email_message.get_payload(decode=True).decode(charset, errors='ignore') + return body + except Exception as e: + logging.error(f"解码邮件正文失败: {e}") + return "" + + def _get_latest_mail_code(self): + """获取最新邮件的验证码""" + try: + # 配置请求会话 + self.session.verify = False + self.session.proxies = {"http": None, "https": None} + self.session.timeout = 30 + + # 获取邮件列表 + mail_list_url = f"https://tempmail.plus/api/mails?email={self.username}{self.emailExtension}&limit=20" + if self.epin: + mail_list_url += f"&epin={self.epin}" + + print(f"正在请求邮件列表: {mail_list_url}") + mail_list_response = self.session.get(mail_list_url) + mail_list_data = mail_list_response.json() + + if not mail_list_data.get("result"): + print("获取邮件列表失败") + return None, None + + # 获取最新邮件的ID + first_id = mail_list_data.get("first_id") + if not first_id: + print("未找到新邮件") + return None, None + + # 获取具体邮件内容 + mail_detail_url = f"https://tempmail.plus/api/mails/{first_id}?email={self.username}{self.emailExtension}" + if self.epin: + mail_detail_url += f"&epin={self.epin}" + + print(f"正在获取邮件内容: {mail_detail_url}") + mail_detail_response = self.session.get(mail_detail_url) + mail_detail_data = mail_detail_response.json() + + if not mail_detail_data.get("result"): + print("获取邮件内容失败") + return None, None + + # 从邮件文本中提取6位数字验证码 + mail_text = mail_detail_data.get("text", "") + code_match = re.search(r"(? 0: + time.sleep(3) + if retry >= 20: + raise Exception("获取验证码超时") + try: + # 连接到IMAP服务器 + mail = imaplib.IMAP4_SSL(self.imap['imap_server'], self.imap['imap_port']) + mail.login(self.imap['imap_user'], self.imap['imap_pass']) + mail.select(self.imap['imap_dir']) + + status, messages = mail.search(None, 'FROM', '"no-reply@cursor.sh"') + if status != 'OK': + return None + + mail_ids = messages[0].split() + if not mail_ids: + # 没有获取到,就在获取一次 + return self._get_mail_code_by_imap(retry=retry + 1) + + latest_mail_id = mail_ids[-1] + + # 获取邮件内容 + status, msg_data = mail.fetch(latest_mail_id, '(RFC822)') + if status != 'OK': + return None + + raw_email = msg_data[0][1] + email_message = email.message_from_bytes(raw_email) + + # 提取邮件正文 + body = self._extract_imap_body(email_message) + if body: + # 使用正则表达式查找6位数字验证码 + code_match = re.search(r"\b\d{6}\b", body) + if code_match: + code = code_match.group() + # 删除邮件 + mail.store(latest_mail_id, '+FLAGS', '\\Deleted') + mail.expunge() + mail.logout() + # print(f"找到的验证码: {code}") + return code + # print("未找到验证码") + mail.logout() + return None + except Exception as e: + print(f"发生错误: {e}") + return None + + def _extract_imap_body(self, email_message): + # 提取邮件正文 + if email_message.is_multipart(): + for part in email_message.walk(): + content_type = part.get_content_type() + content_disposition = str(part.get("Content-Disposition")) + if content_type == "text/plain" and "attachment" not in content_disposition: + charset = part.get_content_charset() or 'utf-8' + try: + body = part.get_payload(decode=True).decode(charset, errors='ignore') + return body + except Exception as e: + logging.error(f"解码邮件正文失败: {e}") + else: + content_type = email_message.get_content_type() + if content_type == "text/plain": + charset = email_message.get_content_charset() or 'utf-8' + try: + body = email_message.get_payload(decode=True).decode(charset, errors='ignore') + return body + except Exception as e: + logging.error(f"解码邮件正文失败: {e}") + return "" + + def _get_latest_mail_code(self): + # 获取邮件列表 + mail_list_url = f"https://tempmail.plus/api/mails?email={self.username}{self.emailExtension}&limit=20&epin={self.epin}" + mail_list_response = self.session.get(mail_list_url) + mail_list_data = mail_list_response.json() + time.sleep(0.5) + + if not mail_list_data.get("result"): + return None, None + + # 获取最新邮件的ID + first_id = mail_list_data.get("first_id") + if not first_id: + return None, None + + # 获取具体邮件内容 + mail_detail_url = f"https://tempmail.plus/api/mails/{first_id}?email={self.username}{self.emailExtension}&epin={self.epin}" + mail_detail_response = self.session.get(mail_detail_url) + mail_detail_data = mail_detail_response.json() + time.sleep(0.5) + + if not mail_detail_data.get("result"): + return None, None + + # 从邮件文本中提取6位数字验证码 + mail_text = mail_detail_data.get("text", "") + code_match = re.search(r"(? logging.Logger: + """配置并返回logger实例""" + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + handler = logging.StreamHandler() + formatter = logging.Formatter( + "%(asctime)s - %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S" + ) + handler.setFormatter(formatter) + logger.addHandler(handler) + return logger + + +logger = setup_logging() + + +def get_cursor_paths() -> Tuple[str, str]: + """ + 根据不同操作系统获取 Cursor 相关路径 + + Returns: + Tuple[str, str]: (package.json路径, main.js路径)的元组 + + Raises: + OSError: 当找不到有效路径或系统不支持时抛出 + """ + system = platform.system() + + paths_map = { + "Darwin": { + "base": "/Applications/Cursor.app/Contents/Resources/app", + "package": "package.json", + "main": "out/main.js", + }, + "Windows": { + "base": os.path.join( + os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app" + ), + "package": "package.json", + "main": "out/main.js", + }, + "Linux": { + "bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app"], + "package": "package.json", + "main": "out/main.js", + }, + } + + if system not in paths_map: + raise OSError(f"不支持的操作系统: {system}") + + if system == "Linux": + for base in paths_map["Linux"]["bases"]: + pkg_path = os.path.join(base, paths_map["Linux"]["package"]) + if os.path.exists(pkg_path): + return (pkg_path, os.path.join(base, paths_map["Linux"]["main"])) + raise OSError("在 Linux 系统上未找到 Cursor 安装路径") + + base_path = paths_map[system]["base"] + return ( + os.path.join(base_path, paths_map[system]["package"]), + os.path.join(base_path, paths_map[system]["main"]), + ) + + +def check_system_requirements(pkg_path: str, main_path: str) -> bool: + """ + 检查系统要求 + + Args: + pkg_path: package.json 文件路径 + main_path: main.js 文件路径 + + Returns: + bool: 检查是否通过 + """ + for file_path in [pkg_path, main_path]: + if not os.path.isfile(file_path): + logger.error(f"文件不存在: {file_path}") + return False + + if not os.access(file_path, os.W_OK): + logger.error(f"没有文件写入权限: {file_path}") + return False + + return True + + +def version_check(version: str, min_version: str = "", max_version: str = "") -> bool: + """ + 版本号检查 + + Args: + version: 当前版本号 + min_version: 最小版本号要求 + max_version: 最大版本号要求 + + Returns: + bool: 版本号是否符合要求 + """ + version_pattern = r"^\d+\.\d+\.\d+$" + try: + if not re.match(version_pattern, version): + logger.error(f"无效的版本号格式: {version}") + return False + + def parse_version(ver: str) -> Tuple[int, ...]: + return tuple(map(int, ver.split("."))) + + current = parse_version(version) + + if min_version and current < parse_version(min_version): + logger.error(f"版本号 {version} 小于最小要求 {min_version}") + return False + + if max_version and current > parse_version(max_version): + logger.error(f"版本号 {version} 大于最大要求 {max_version}") + return False + + return True + + except Exception as e: + logger.error(f"版本检查失败: {str(e)}") + return False + + +def modify_main_js(main_path: str) -> bool: + """ + 修改 main.js 文件 + + Args: + main_path: main.js 文件路径 + + Returns: + bool: 修改是否成功 + """ + try: + # 获取原始文件的权限和所有者信息 + original_stat = os.stat(main_path) + original_mode = original_stat.st_mode + original_uid = original_stat.st_uid + original_gid = original_stat.st_gid + + with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_file: + with open(main_path, "r", encoding="utf-8") as main_file: + content = main_file.read() + + # 执行替换 + patterns = { + r"async getMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMachineId(){return \1}", + r"async getMacMachineId\(\)\{return [^??]+\?\?([^}]+)\}": r"async getMacMachineId(){return \1}", + } + + for pattern, replacement in patterns.items(): + content = re.sub(pattern, replacement, content) + + tmp_file.write(content) + tmp_path = tmp_file.name + + # 使用 shutil.copy2 保留文件权限 + shutil.copy2(main_path, main_path + ".old") + shutil.move(tmp_path, main_path) + + # 恢复原始文件的权限和所有者 + os.chmod(main_path, original_mode) + if os.name != "nt": # 在非Windows系统上设置所有者 + os.chown(main_path, original_uid, original_gid) + + logger.info("文件修改成功") + return True + + except Exception as e: + logger.error(f"修改文件时发生错误: {str(e)}") + if "tmp_path" in locals(): + os.unlink(tmp_path) + return False + + +def backup_files(pkg_path: str, main_path: str) -> bool: + """ + 备份原始文件 + + Args: + pkg_path: package.json 文件路径(未使用) + main_path: main.js 文件路径 + + Returns: + bool: 备份是否成功 + """ + try: + # 只备份 main.js + if os.path.exists(main_path): + backup_main = f"{main_path}.bak" + shutil.copy2(main_path, backup_main) + logger.info(f"已备份 main.js: {backup_main}") + + return True + except Exception as e: + logger.error(f"备份文件失败: {str(e)}") + return False + + +def restore_backup_files(pkg_path: str, main_path: str) -> bool: + """ + 恢复备份文件 + + Args: + pkg_path: package.json 文件路径(未使用) + main_path: main.js 文件路径 + + Returns: + bool: 恢复是否成功 + """ + try: + # 只恢复 main.js + backup_main = f"{main_path}.bak" + if os.path.exists(backup_main): + shutil.copy2(backup_main, main_path) + logger.info(f"已恢复 main.js") + return True + + logger.error("未找到备份文件") + return False + except Exception as e: + logger.error(f"恢复备份失败: {str(e)}") + return False + + +def patch_cursor_get_machine_id(restore_mode=False) -> None: + """ + 主函数 + + Args: + restore_mode: 是否为恢复模式 + """ + logger.info("开始执行脚本...") + + try: + # 获取路径 + pkg_path, main_path = get_cursor_paths() + + # 检查系统要求 + if not check_system_requirements(pkg_path, main_path): + sys.exit(1) + + if restore_mode: + # 恢复备份 + if restore_backup_files(pkg_path, main_path): + logger.info("备份恢复完成") + else: + logger.error("备份恢复失败") + return + + # 获取版本号 + try: + with open(pkg_path, "r", encoding="utf-8") as f: + version = json.load(f)["version"] + logger.info(f"当前 Cursor 版本: {version}") + except Exception as e: + logger.error(f"无法读取版本号: {str(e)}") + sys.exit(1) + + # 检查版本 + if not version_check(version, min_version="0.45.0"): + logger.error("版本不符合要求(需 >= 0.45.x)") + sys.exit(1) + + logger.info("版本检查通过,准备修改文件") + + # 备份文件 + if not backup_files(pkg_path, main_path): + logger.error("文件备份失败,终止操作") + sys.exit(1) + + # 修改文件 + if not modify_main_js(main_path): + sys.exit(1) + + logger.info("脚本执行完成") + + except Exception as e: + logger.error(f"执行过程中发生错误: {str(e)}") + sys.exit(1) + + +if __name__ == "__main__": + patch_cursor_get_machine_id() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..af715ad --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +DrissionPage==4.1.0.9 +colorama==0.4.6 +python-dotenv==1.0.0 +pyinstaller +tqdm==4.66.1 \ No newline at end of file diff --git a/reset_machine.py b/reset_machine.py new file mode 100644 index 0000000..ddb9039 --- /dev/null +++ b/reset_machine.py @@ -0,0 +1,134 @@ +import os +import sys +import json +import uuid +import hashlib +import shutil +from colorama import Fore, Style, init + +# 初始化colorama +init() + +# 定义emoji和颜色常量 +EMOJI = { + "FILE": "📄", + "BACKUP": "💾", + "SUCCESS": "✅", + "ERROR": "❌", + "INFO": "ℹ️", + "RESET": "🔄", +} + + +class MachineIDResetter: + def __init__(self): + # 判断操作系统 + if sys.platform == "win32": # Windows + appdata = os.getenv("APPDATA") + if appdata is None: + raise EnvironmentError("APPDATA 环境变量未设置") + self.db_path = os.path.join( + appdata, "Cursor", "User", "globalStorage", "storage.json" + ) + elif sys.platform == "darwin": # macOS + self.db_path = os.path.abspath( + os.path.expanduser( + "~/Library/Application Support/Cursor/User/globalStorage/storage.json" + ) + ) + elif sys.platform == "linux": # Linux 和其他类Unix系统 + self.db_path = os.path.abspath( + os.path.expanduser("~/.config/Cursor/User/globalStorage/storage.json") + ) + else: + raise NotImplementedError(f"不支持的操作系统: {sys.platform}") + + def generate_new_ids(self): + """生成新的机器ID""" + # 生成新的UUID + dev_device_id = str(uuid.uuid4()) + + # 生成新的machineId (64个字符的十六进制) + machine_id = hashlib.sha256(os.urandom(32)).hexdigest() + + # 生成新的macMachineId (128个字符的十六进制) + mac_machine_id = hashlib.sha512(os.urandom(64)).hexdigest() + + # 生成新的sqmId + sqm_id = "{" + str(uuid.uuid4()).upper() + "}" + + return { + "telemetry.devDeviceId": dev_device_id, + "telemetry.macMachineId": mac_machine_id, + "telemetry.machineId": machine_id, + "telemetry.sqmId": sqm_id, + } + + def reset_machine_ids(self): + """重置机器ID并备份原文件""" + try: + print(f"{Fore.CYAN}{EMOJI['INFO']} 正在检查配置文件...{Style.RESET_ALL}") + + # 检查文件是否存在 + if not os.path.exists(self.db_path): + print( + f"{Fore.RED}{EMOJI['ERROR']} 配置文件不存在: {self.db_path}{Style.RESET_ALL}" + ) + return False + + # 检查文件权限 + if not os.access(self.db_path, os.R_OK | os.W_OK): + print( + f"{Fore.RED}{EMOJI['ERROR']} 无法读写配置文件,请检查文件权限!{Style.RESET_ALL}" + ) + print( + f"{Fore.RED}{EMOJI['ERROR']} 如果你使用过 go-cursor-help 来修改 ID; 请修改文件只读权限 {self.db_path} {Style.RESET_ALL}" + ) + return False + + # 读取现有配置 + print(f"{Fore.CYAN}{EMOJI['FILE']} 读取当前配置...{Style.RESET_ALL}") + with open(self.db_path, "r", encoding="utf-8") as f: + config = json.load(f) + + # 生成新的ID + print(f"{Fore.CYAN}{EMOJI['RESET']} 生成新的机器标识...{Style.RESET_ALL}") + new_ids = self.generate_new_ids() + + # 更新配置 + config.update(new_ids) + + # 保存新配置 + print(f"{Fore.CYAN}{EMOJI['FILE']} 保存新配置...{Style.RESET_ALL}") + with open(self.db_path, "w", encoding="utf-8") as f: + json.dump(config, f, indent=4) + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} 机器标识重置成功!{Style.RESET_ALL}") + print(f"\n{Fore.CYAN}新的机器标识:{Style.RESET_ALL}") + for key, value in new_ids.items(): + print(f"{EMOJI['INFO']} {key}: {Fore.GREEN}{value}{Style.RESET_ALL}") + + return True + + except PermissionError as e: + print(f"{Fore.RED}{EMOJI['ERROR']} 权限错误: {str(e)}{Style.RESET_ALL}") + print( + f"{Fore.YELLOW}{EMOJI['INFO']} 请尝试以管理员身份运行此程序{Style.RESET_ALL}" + ) + return False + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} 重置过程出错: {str(e)}{Style.RESET_ALL}") + + return False + + +if __name__ == "__main__": + print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['RESET']} Cursor 机器标识重置工具{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}") + + resetter = MachineIDResetter() + resetter.reset_machine_ids() + + print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") + input(f"{EMOJI['INFO']} 按回车键退出...") diff --git a/reset_machine_enhanced.py b/reset_machine_enhanced.py new file mode 100644 index 0000000..b9c87a4 --- /dev/null +++ b/reset_machine_enhanced.py @@ -0,0 +1,176 @@ +import os +import sys +import json +import uuid +import hashlib +import subprocess +import winreg +from datetime import datetime +from colorama import Fore, Style, init + +# 初始化colorama +init() + +EMOJI = { + "FILE": "📄", + "BACKUP": "💾", + "SUCCESS": "✅", + "ERROR": "❌", + "INFO": "ℹ️", + "RESET": "🔄", +} + +class EnhancedMachineIDResetter: + def __init__(self): + if sys.platform != "win32": + raise NotImplementedError("此版本仅支持Windows系统") + + self.appdata = os.getenv("APPDATA") + self.localappdata = os.getenv("LOCALAPPDATA") + if not self.appdata or not self.localappdata: + raise EnvironmentError("环境变量未设置") + + self.cursor_storage = os.path.join( + self.appdata, "Cursor", "User", "globalStorage", "storage.json" + ) + self.backup_dir = os.path.join( + self.appdata, "Cursor", "User", "globalStorage", "backups" + ) + + def backup_registry(self): + """备份注册表MachineGuid""" + try: + # 创建备份目录 + os.makedirs(self.backup_dir, exist_ok=True) + + # 读取当前MachineGuid + with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\\Microsoft\\Cryptography", 0, winreg.KEY_READ | winreg.KEY_WOW64_64KEY) as key: + machine_guid = winreg.QueryValueEx(key, "MachineGuid")[0] + + # 保存备份 + backup_time = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_file = os.path.join(self.backup_dir, f"MachineGuid.backup_{backup_time}") + with open(backup_file, "w") as f: + f.write(machine_guid) + + print(f"{Fore.GREEN}{EMOJI['BACKUP']} 注册表备份成功: {backup_file}{Style.RESET_ALL}") + return True + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} 注册表备份失败: {str(e)}{Style.RESET_ALL}") + return False + + def modify_registry(self): + """修改注册表MachineGuid""" + try: + new_guid = str(uuid.uuid4()) + + # 使用管理员权限修改注册表 + cmd = f'reg add "HKLM\\SOFTWARE\\Microsoft\\Cryptography" /v MachineGuid /t REG_SZ /d "{new_guid}" /f' + subprocess.run(["powershell", "Start-Process", "cmd", "/c", cmd, "-Verb", "RunAs", "-WindowStyle", "Hidden"]) + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} 注册表MachineGuid已更新: {new_guid}{Style.RESET_ALL}") + return True + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} 注册表修改失败: {str(e)}{Style.RESET_ALL}") + return False + + def generate_new_ids(self): + """生成新的机器标识""" + return { + "telemetry.devDeviceId": str(uuid.uuid4()), + "telemetry.macMachineId": hashlib.sha512(os.urandom(64)).hexdigest(), + "telemetry.machineId": hashlib.sha256(os.urandom(32)).hexdigest(), + "telemetry.sqmId": "{" + str(uuid.uuid4()).upper() + "}", + } + + def backup_cursor_config(self): + """备份Cursor配置文件""" + try: + if os.path.exists(self.cursor_storage): + backup_time = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_file = os.path.join(self.backup_dir, f"storage.json.backup_{backup_time}") + os.makedirs(self.backup_dir, exist_ok=True) + with open(self.cursor_storage, "r", encoding="utf-8") as src, \ + open(backup_file, "w", encoding="utf-8") as dst: + dst.write(src.read()) + print(f"{Fore.GREEN}{EMOJI['BACKUP']} Cursor配置备份成功: {backup_file}{Style.RESET_ALL}") + return True + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} Cursor配置备份失败: {str(e)}{Style.RESET_ALL}") + return False + + def modify_cursor_config(self): + """修改Cursor配置文件""" + try: + if not os.path.exists(self.cursor_storage): + print(f"{Fore.RED}{EMOJI['ERROR']} Cursor配置文件不存在{Style.RESET_ALL}") + return False + + # 读取现有配置 + with open(self.cursor_storage, "r", encoding="utf-8") as f: + config = json.load(f) + + # 更新配置 + new_ids = self.generate_new_ids() + config.update(new_ids) + + # 保存新配置 + with open(self.cursor_storage, "w", encoding="utf-8") as f: + json.dump(config, f, indent=4) + + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Cursor配置已更新{Style.RESET_ALL}") + for key, value in new_ids.items(): + print(f"{EMOJI['INFO']} {key}: {value}") + return True + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} Cursor配置修改失败: {str(e)}{Style.RESET_ALL}") + return False + + def kill_cursor_process(self): + """结束Cursor进程""" + try: + subprocess.run(["taskkill", "/F", "/IM", "Cursor.exe"], capture_output=True) + print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Cursor进程已终止{Style.RESET_ALL}") + return True + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} 终止Cursor进程失败: {str(e)}{Style.RESET_ALL}") + return False + + def reset_all(self): + """执行完整的重置流程""" + print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") + print(f"{Fore.CYAN}{EMOJI['RESET']} Cursor 增强版机器码重置工具{Style.RESET_ALL}") + print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}\n") + + # 1. 结束Cursor进程 + print(f"{Fore.CYAN}[1/5] 正在结束Cursor进程...{Style.RESET_ALL}") + self.kill_cursor_process() + + # 2. 备份注册表 + print(f"\n{Fore.CYAN}[2/5] 正在备份注册表...{Style.RESET_ALL}") + self.backup_registry() + + # 3. 备份Cursor配置 + print(f"\n{Fore.CYAN}[3/5] 正在备份Cursor配置...{Style.RESET_ALL}") + self.backup_cursor_config() + + # 4. 修改注册表 + print(f"\n{Fore.CYAN}[4/5] 正在修改注册表...{Style.RESET_ALL}") + self.modify_registry() + + # 5. 修改Cursor配置 + print(f"\n{Fore.CYAN}[5/5] 正在修改Cursor配置...{Style.RESET_ALL}") + self.modify_cursor_config() + + print(f"\n{Fore.GREEN}{EMOJI['SUCCESS']} 重置完成!{Style.RESET_ALL}") + print(f"{Fore.YELLOW}{EMOJI['INFO']} 请重启电脑以使更改生效{Style.RESET_ALL}") + +if __name__ == "__main__": + try: + resetter = EnhancedMachineIDResetter() + resetter.reset_all() + except Exception as e: + print(f"{Fore.RED}{EMOJI['ERROR']} 发生错误: {str(e)}{Style.RESET_ALL}") + finally: + print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") + input(f"{EMOJI['INFO']} 按回车键退出...") \ No newline at end of file diff --git a/screen/28613e3f3f23a935b66a7ba31ff4e3f.jpg b/screen/28613e3f3f23a935b66a7ba31ff4e3f.jpg new file mode 100644 index 0000000..2039cb3 Binary files /dev/null and b/screen/28613e3f3f23a935b66a7ba31ff4e3f.jpg differ diff --git a/screen/afdian-[未认证]阿臻.jpg b/screen/afdian-[未认证]阿臻.jpg new file mode 100644 index 0000000..d76b743 Binary files /dev/null and b/screen/afdian-[未认证]阿臻.jpg differ diff --git a/screen/c29ea438-ee74-4ba1-bbf6-25e622cdfad5.png b/screen/c29ea438-ee74-4ba1-bbf6-25e622cdfad5.png new file mode 100644 index 0000000..db46e78 Binary files /dev/null and b/screen/c29ea438-ee74-4ba1-bbf6-25e622cdfad5.png differ diff --git a/screen/mm_facetoface_collect_qrcode_1738583247120.png b/screen/mm_facetoface_collect_qrcode_1738583247120.png new file mode 100644 index 0000000..780fe33 Binary files /dev/null and b/screen/mm_facetoface_collect_qrcode_1738583247120.png differ diff --git a/screen/qrcode_for_gh_c985615b5f2b_258.jpg b/screen/qrcode_for_gh_c985615b5f2b_258.jpg new file mode 100644 index 0000000..92a714e Binary files /dev/null and b/screen/qrcode_for_gh_c985615b5f2b_258.jpg differ diff --git a/screen/截屏2025-01-04 09.44.48.png b/screen/截屏2025-01-04 09.44.48.png new file mode 100644 index 0000000..5ca8ecb Binary files /dev/null and b/screen/截屏2025-01-04 09.44.48.png differ diff --git a/test_api.py b/test_api.py new file mode 100644 index 0000000..e1121df --- /dev/null +++ b/test_api.py @@ -0,0 +1,24 @@ +import requests +import json + +# 禁用 SSL 警告 +import urllib3 +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +# 测试数据 +test_data = { + 'keyword': '12132ed@qq.com' +} + +# 发送请求 +response = requests.post( + 'https://cursorapi.nosqli.com/admin/api.member/check', + json=test_data, + verify=False, + proxies={"http": None, "https": None} +) + +# 打印响应 +print("Status Code:", response.status_code) +print("\nResponse:") +print(json.dumps(response.json(), indent=2, ensure_ascii=False)) \ No newline at end of file diff --git a/test_email.py b/test_email.py new file mode 100644 index 0000000..1ce067d --- /dev/null +++ b/test_email.py @@ -0,0 +1,43 @@ +import requests +import json +import urllib3 + +# 禁用SSL警告 +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +def test_specific_email(): + print("开始测试指定邮箱API...") + + # 创建session并配置 + session = requests.Session() + session.verify = False + session.trust_env = False + + # 测试URL + test_url = "https://tempmail.plus/api/mails?email=ademyyk@mailto.plus&limit=20&epin=" + + try: + # 不使用代理直接测试 + print("\n1. 直接连接测试...") + response = session.get(test_url) + print(f"状态码: {response.status_code}") + print(f"响应内容: {json.dumps(response.json(), indent=2, ensure_ascii=False)}") + + except Exception as e: + print(f"\n直接连接出错: {e}") + + # 如果直接连接失败,尝试使用代理 + try: + print("\n2. 尝试使用代理...") + proxies = { + 'http': 'http://127.0.0.1:7890', + 'https': 'http://127.0.0.1:7890' + } + response = session.get(test_url, proxies=proxies) + print(f"状态码: {response.status_code}") + print(f"响应内容: {json.dumps(response.json(), indent=2, ensure_ascii=False)}") + except Exception as e: + print(f"\n代理连接也出错: {e}") + +if __name__ == "__main__": + test_specific_email() \ No newline at end of file diff --git a/turnstilePatch/manifest.json b/turnstilePatch/manifest.json new file mode 100644 index 0000000..3f4c606 --- /dev/null +++ b/turnstilePatch/manifest.json @@ -0,0 +1,18 @@ +{ + "manifest_version": 3, + "name": "Turnstile Patcher", + "version": "2.1", + "content_scripts": [ + { + "js": [ + "./script.js" + ], + "matches": [ + "" + ], + "run_at": "document_start", + "all_frames": true, + "world": "MAIN" + } + ] +} \ No newline at end of file diff --git a/turnstilePatch/readme.txt b/turnstilePatch/readme.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/turnstilePatch/readme.txt @@ -0,0 +1 @@ + diff --git a/turnstilePatch/script.js b/turnstilePatch/script.js new file mode 100644 index 0000000..a46d798 --- /dev/null +++ b/turnstilePatch/script.js @@ -0,0 +1,12 @@ +function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +// old method wouldn't work on 4k screens + +let screenX = getRandomInt(800, 1200); +let screenY = getRandomInt(400, 600); + +Object.defineProperty(MouseEvent.prototype, 'screenX', { value: screenX }); + +Object.defineProperty(MouseEvent.prototype, 'screenY', { value: screenY }); \ No newline at end of file