Merge pull request #1 from XnsYT/main

feat: Add enhanced configuration, error handling and utility systems …
This commit is contained in:
psipher
2025-09-24 16:48:17 +02:00
committed by GitHub
15 changed files with 3541 additions and 904 deletions

View File

@@ -1,5 +1,26 @@
# Change Log
## v1.11.04
1. Add: Enhanced Configuration Management System | 新增增强配置管理系统
- Multi-format support (INI, JSON, YAML) | 多格式支持 (INI, JSON, YAML)
- Automatic configuration validation | 自动配置验证
- Backup and restore functionality | 备份和恢复功能
- Platform-specific path management | 平台特定路径管理
2. Add: Enhanced Error Handling System | 新增增强错误处理系统
- Automatic error categorization | 自动错误分类
- Severity-based logging | 基于严重性的日志记录
- Recovery strategies with retry logic | 带重试逻辑的恢复策略
- Error history tracking | 错误历史跟踪
3. Add: Enhanced Utility System | 新增增强工具系统
- Advanced path management | 高级路径管理
- Multi-browser support with detection | 多浏览器支持与检测
- Process monitoring and management | 进程监控和管理
- System information gathering | 系统信息收集
- Network connectivity testing | 网络连接测试
4. Improve: Code robustness and maintainability | 改进代码健壮性和可维护性
5. Fix: Type safety issues and linter errors | 修复类型安全问题和linter错误
6. Fix: Some Issues | 修复一些问题
## v1.11.03
1. Update: TempMailPlus Cursor Email Detection Logic | 更新 TempMailPlus Cursor 邮件识别逻辑
2. Fix: Windows User Directory Path | 修正 windows 环境下用户目录的获取方式

View File

@@ -70,6 +70,20 @@ For optimal performance, run with privileges and always stay up to date.
* Multi-language support (English, 简体中文, 繁體中文, Vietnamese)<br>多語言支持(英文、简体中文、繁體中文、越南語)<br>
## 🚀 Recent Improvements | 最近改進
* **Enhanced Error Handling**: Robust error handling with detailed logging for better troubleshooting<br>增強的錯誤處理:具有詳細日誌記錄的強大錯誤處理,以便更好地進行故障排除<br>
* **Improved Configuration Management**: Centralized configuration with type validation and better defaults<br>改進的配置管理:集中配置,具有類型驗證和更好的默認值<br>
* **Code Refactoring**: Better code organization with proper typing and documentation<br>代碼重構:通過適當的類型和文檔實現更好的代碼組織<br>
* **Enhanced Process Management**: Better detection and management of Cursor processes across all platforms<br>增強的進程管理:在所有平台上更好地檢測和管理 Cursor 進程<br>
* **Token Management**: Improved token validation, refresh, and extraction logic<br>令牌管理:改進的令牌驗證、刷新和提取邏輯<br>
* **Cross-Platform Compatibility**: Better handling of platform-specific paths and behaviors<br>跨平台兼容性:更好地處理特定於平台的路徑和行為<br>
## 💻 System Support | 系統支持
| Operating System | Architecture | Supported |

View File

@@ -3,14 +3,26 @@ import shutil
import platform
import tempfile
import glob
import logging
from colorama import Fore, Style, init
import configparser
import sys
from config import get_config
from datetime import datetime
from typing import Optional, Dict, List, Union, Tuple, Any
from pathlib import Path
from config import get_config
# Initialize colorama
init()
init(autoreset=True)
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger(__name__)
# Define emoji constants
EMOJI = {
@@ -23,39 +35,45 @@ EMOJI = {
"WARNING": "⚠️",
}
def get_user_documents_path():
"""Get user Documents folder path"""
if sys.platform == "win32":
try:
import winreg
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders") as key:
documents_path, _ = winreg.QueryValueEx(key, "Personal")
return documents_path
except Exception as e:
# fallback
return os.path.join(os.path.expanduser("~"), "Documents")
elif sys.platform == "darwin":
return os.path.join(os.path.expanduser("~"), "Documents")
else: # Linux
# Get actual user's home directory
sudo_user = os.environ.get('SUDO_USER')
if sudo_user:
return os.path.join("/home", sudo_user, "Documents")
return os.path.join(os.path.expanduser("~"), "Documents")
def get_user_documents_path() -> str:
"""Get user Documents folder path"""
if sys.platform == "win32":
try:
import winreg
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders") as key:
documents_path, _ = winreg.QueryValueEx(key, "Personal")
return documents_path
except Exception as e:
logger.warning(f"Failed to get Documents path from registry: {e}")
return os.path.join(os.path.expanduser("~"), "Documents")
elif sys.platform == "darwin":
return os.path.join(os.path.expanduser("~"), "Documents")
else: # Linux
# Get actual user's home directory
sudo_user = os.environ.get('SUDO_USER')
if sudo_user:
return os.path.join("/home", sudo_user, "Documents")
return os.path.join(os.path.expanduser("~"), "Documents")
def get_workbench_cursor_path(translator=None) -> str:
"""Get Cursor workbench.desktop.main.js path"""
def get_workbench_cursor_path(translator: Any = None) -> str:
"""Get Cursor workbench.desktop.main.js path
Args:
translator: Optional translator for internationalization
Returns:
str: Path to the workbench.desktop.main.js file
Raises:
OSError: If the file is not found or the OS is not supported
"""
system = platform.system()
# Read configuration
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
config = configparser.ConfigParser()
if os.path.exists(config_file):
config.read(config_file)
config = get_config(translator)
# Define paths for different operating systems
paths_map = {
"Darwin": { # macOS
"base": "/Applications/Cursor.app/Contents/Resources/app",
@@ -65,53 +83,81 @@ def get_workbench_cursor_path(translator=None) -> str:
"main": "out\\vs\\workbench\\workbench.desktop.main.js"
},
"Linux": {
"bases": ["/opt/Cursor/resources/app", "/usr/share/cursor/resources/app", "/usr/lib/cursor/app/"],
"bases": [
"/opt/Cursor/resources/app",
"/usr/share/cursor/resources/app",
"/usr/lib/cursor/app/"
],
"main": "out/vs/workbench/workbench.desktop.main.js"
}
}
# Add extracted AppImage paths for Linux
if system == "Linux":
# Add extracted AppImage with correct usr structure
extracted_usr_paths = glob.glob(os.path.expanduser("~/squashfs-root/usr/share/cursor/resources/app"))
paths_map["Linux"]["bases"].extend(extracted_usr_paths)
# Check if the system is supported
if system not in paths_map:
raise OSError(translator.get('reset.unsupported_os', system=system) if translator else f"不支持的操作系统: {system}")
error_msg = f"Unsupported operating system: {system}"
logger.error(error_msg)
raise OSError(translator.get('reset.unsupported_os', system=system) if translator else error_msg)
# For Linux, check all possible base paths
if system == "Linux":
for base in paths_map["Linux"]["bases"]:
main_path = os.path.join(base, paths_map["Linux"]["main"])
print(f"{Fore.CYAN}{EMOJI['INFO']} Checking path: {main_path}{Style.RESET_ALL}")
logger.info(f"Checking path: {main_path}")
if os.path.exists(main_path):
return main_path
# For Windows and macOS, get the base path from config
if system == "Windows":
base_path = config.get('WindowsPaths', 'cursor_path')
if config and config.has_section('WindowsPaths') and config.has_option('WindowsPaths', 'cursor_path'):
base_path = config.get('WindowsPaths', 'cursor_path')
else:
logger.warning("WindowsPaths section or cursor_path option not found in config")
base_path = os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Cursor', 'resources', 'app')
elif system == "Darwin":
base_path = paths_map[system]["base"]
if config.has_section('MacPaths') and config.has_option('MacPaths', 'cursor_path'):
if config and config.has_section('MacPaths') and config.has_option('MacPaths', 'cursor_path'):
base_path = config.get('MacPaths', 'cursor_path')
else: # Linux
# For Linux, we've already checked all bases in the loop above
# If we're here, it means none of the bases worked, so we'll use the first one
base_path = paths_map[system]["bases"][0]
if config.has_section('LinuxPaths') and config.has_option('LinuxPaths', 'cursor_path'):
else:
base_path = paths_map[system]["base"]
else: # Linux (fallback if none of the bases worked)
if config and config.has_section('LinuxPaths') and config.has_option('LinuxPaths', 'cursor_path'):
base_path = config.get('LinuxPaths', 'cursor_path')
else:
base_path = paths_map[system]["bases"][0]
# Construct the full path to the main.js file
main_path = os.path.join(base_path, paths_map[system]["main"])
# Check if the file exists
if not os.path.exists(main_path):
raise OSError(translator.get('reset.file_not_found', path=main_path) if translator else f"未找到 Cursor main.js 文件: {main_path}")
error_msg = f"Cursor main.js file not found: {main_path}"
logger.error(error_msg)
raise OSError(translator.get('reset.file_not_found', path=main_path) if translator else error_msg)
return main_path
def modify_workbench_js(file_path: str, translator=None) -> bool:
def modify_workbench_js(file_path: str, translator: Any = None) -> bool:
"""
Modify file content
Modify workbench.desktop.main.js file to bypass token limit
Args:
file_path: Path to the workbench.desktop.main.js file
translator: Optional translator for internationalization
Returns:
bool: True if the modification was successful, False otherwise
"""
try:
# Check if file exists
if not os.path.exists(file_path):
logger.error(f"File not found: {file_path}")
return False
# Save original file permissions
original_stat = os.stat(file_path)
original_mode = original_stat.st_mode
@@ -121,33 +167,46 @@ def modify_workbench_js(file_path: str, translator=None) -> bool:
# Create temporary file
with tempfile.NamedTemporaryFile(mode="w", encoding="utf-8", errors="ignore", delete=False) as tmp_file:
# Read original content
with open(file_path, "r", encoding="utf-8", errors="ignore") as main_file:
content = main_file.read()
try:
with open(file_path, "r", encoding="utf-8", errors="ignore") as main_file:
content = main_file.read()
except Exception as e:
logger.error(f"Failed to read file: {e}")
os.unlink(tmp_file.name)
return False
# Define patterns to replace
patterns = {
# 通用按钮替换模式
r'B(k,D(Ln,{title:"Upgrade to Pro",size:"small",get codicon(){return A.rocket},get onClick(){return t.pay}}),null)': r'B(k,D(Ln,{title:"yeongpin GitHub",size:"small",get codicon(){return A.github},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)',
# Button replacement patterns
r'B(k,D(Ln,{title:"Upgrade to Pro",size:"small",get codicon(){return A.rocket},get onClick(){return t.pay}}),null)':
r'B(k,D(Ln,{title:"yeongpin GitHub",size:"small",get codicon(){return A.github},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)',
# Windows/Linux
r'M(x,I(as,{title:"Upgrade to Pro",size:"small",get codicon(){return $.rocket},get onClick(){return t.pay}}),null)': r'M(x,I(as,{title:"yeongpin GitHub",size:"small",get codicon(){return $.github},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)',
r'M(x,I(as,{title:"Upgrade to Pro",size:"small",get codicon(){return $.rocket},get onClick(){return t.pay}}),null)':
r'M(x,I(as,{title:"yeongpin GitHub",size:"small",get codicon(){return $.github},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)',
# Mac 通用按钮替换模式
r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)': r'$(k,E(Ks,{title:"yeongpin GitHub",size:"small",get codicon(){return F.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)',
# Badge 替换
# Mac button replacement pattern
r'$(k,E(Ks,{title:"Upgrade to Pro",size:"small",get codicon(){return F.rocket},get onClick(){return t.pay}}),null)':
r'$(k,E(Ks,{title:"yeongpin GitHub",size:"small",get codicon(){return F.rocket},get onClick(){return function(){window.open("https://github.com/yeongpin/cursor-free-vip","_blank")}}}),null)',
# Badge replacement
r'<div>Pro Trial': r'<div>Pro',
r'py-1">Auto-select': r'py-1">Bypass-Version-Pin',
#
r'async getEffectiveTokenLimit(e){const n=e.modelName;if(!n)return 2e5;':r'async getEffectiveTokenLimit(e){return 9000000;const n=e.modelName;if(!n)return 9e5;',
# Pro
r'var DWr=ne("<div class=settings__item_description>You are currently signed in with <strong></strong>.");': r'var DWr=ne("<div class=settings__item_description>You are currently signed in with <strong></strong>. <h1>Pro</h1>");',
# Token limit bypass
r'async getEffectiveTokenLimit(e){const n=e.modelName;if(!n)return 2e5;':
r'async getEffectiveTokenLimit(e){return 9000000;const n=e.modelName;if(!n)return 9e5;',
# Toast 替换
# Pro status
r'var DWr=ne("<div class=settings__item_description>You are currently signed in with <strong></strong>.");':
r'var DWr=ne("<div class=settings__item_description>You are currently signed in with <strong></strong>. <h1>Pro</h1>");',
# Toast replacement
r'notifications-toasts': r'notifications-toasts hidden'
}
# 使用patterns进行替换
# Apply replacements
for old_pattern, new_pattern in patterns.items():
content = content.replace(old_pattern, new_pattern)
@@ -158,24 +217,40 @@ def modify_workbench_js(file_path: str, translator=None) -> bool:
# Backup original file with timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = f"{file_path}.backup.{timestamp}"
shutil.copy2(file_path, backup_path)
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.backup_created', path=backup_path)}{Style.RESET_ALL}")
try:
shutil.copy2(file_path, backup_path)
logger.info(f"Backup created: {backup_path}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.backup_created', path=backup_path) if translator else f'Backup created: {backup_path}'}{Style.RESET_ALL}")
except Exception as e:
logger.error(f"Failed to create backup: {e}")
os.unlink(tmp_path)
return False
# Move temporary file to original position
if os.path.exists(file_path):
os.remove(file_path)
shutil.move(tmp_path, file_path)
try:
if os.path.exists(file_path):
os.remove(file_path)
shutil.move(tmp_path, file_path)
except Exception as e:
logger.error(f"Failed to replace original file: {e}")
return False
# Restore original permissions
os.chmod(file_path, original_mode)
if os.name != "nt": # Not Windows
os.chown(file_path, original_uid, original_gid)
try:
os.chmod(file_path, original_mode)
if os.name != "nt": # Not Windows
os.chown(file_path, original_uid, original_gid)
except Exception as e:
logger.warning(f"Failed to restore original permissions: {e}")
# Continue anyway as this is not critical
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.file_modified')}{Style.RESET_ALL}")
logger.info("File modified successfully")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('reset.file_modified') if translator else 'File modified successfully'}{Style.RESET_ALL}")
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.modify_file_failed', error=str(e))}{Style.RESET_ALL}")
logger.error(f"Failed to modify file: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('reset.modify_file_failed', error=str(e)) if translator else f'Failed to modify file: {str(e)}'}{Style.RESET_ALL}")
if "tmp_path" in locals():
try:
os.unlink(tmp_path)
@@ -183,19 +258,53 @@ def modify_workbench_js(file_path: str, translator=None) -> bool:
pass
return False
def run(translator=None):
config = get_config(translator)
if not config:
def run(translator: Any = None) -> bool:
"""Run the token limit bypass
Args:
translator: Optional translator for internationalization
Returns:
bool: True if successful, False otherwise
"""
try:
config = get_config(translator)
if not config:
logger.error("Failed to get configuration")
return False
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['RESET']} {translator.get('bypass_token_limit.title') if translator else 'Bypass Token Limit'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
# Get workbench.desktop.main.js path
try:
workbench_path = get_workbench_cursor_path(translator)
logger.info(f"Found workbench.desktop.main.js at: {workbench_path}")
except OSError as e:
logger.error(f"Failed to get workbench path: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {str(e)}{Style.RESET_ALL}")
return False
# Modify the file
success = modify_workbench_js(workbench_path, translator)
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
input(f"{EMOJI['INFO']} {translator.get('bypass_token_limit.press_enter') if translator else 'Press Enter to continue...'}")
return success
except Exception as e:
logger.error(f"Error in run function: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} An unexpected error occurred: {str(e)}{Style.RESET_ALL}")
return False
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['RESET']} {translator.get('bypass_token_limit.title')}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
modify_workbench_js(get_workbench_cursor_path(translator), translator)
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
input(f"{EMOJI['INFO']} {translator.get('bypass_token_limit.press_enter')}...")
if __name__ == "__main__":
from main import translator as main_translator
run(main_translator)
try:
from main import translator as main_translator
run(main_translator)
except ImportError:
logger.warning("Failed to import translator from main.py, running without translation")
run(None)
except Exception as e:
logger.error(f"Error running bypass_token_limit.py: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} An unexpected error occurred: {str(e)}{Style.RESET_ALL}")

View File

@@ -4,13 +4,24 @@ import shutil
import platform
import configparser
import time
import logging
from colorama import Fore, Style, init
import sys
import traceback
from pathlib import Path
from typing import Optional, Dict, List, Union, Tuple, Any
from utils import get_user_documents_path
# Initialize colorama
init()
init(autoreset=True)
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger(__name__)
# Define emoji constants
EMOJI = {
@@ -24,8 +35,18 @@ EMOJI = {
'VERSION': '🏷️'
}
def get_product_json_path(translator=None):
"""Get Cursor product.json path"""
def get_product_json_path(translator: Any = None) -> str:
"""Get Cursor product.json path based on the operating system.
Args:
translator: Optional translator for internationalization
Returns:
str: Path to the product.json file
Raises:
OSError: If the file is not found or the OS is not supported
"""
system = platform.system()
# Read configuration
@@ -36,10 +57,13 @@ def get_product_json_path(translator=None):
if os.path.exists(config_file):
config.read(config_file)
# Define paths for different operating systems
if system == "Windows":
localappdata = os.environ.get("LOCALAPPDATA")
if not localappdata:
raise OSError(translator.get('bypass.localappdata_not_found') if translator else "LOCALAPPDATA environment variable not found")
error_msg = "LOCALAPPDATA environment variable not found"
logger.error(error_msg)
raise OSError(translator.get('bypass.localappdata_not_found') if translator else error_msg)
product_json_path = os.path.join(localappdata, "Programs", "Cursor", "resources", "app", "product.json")
@@ -66,38 +90,72 @@ def get_product_json_path(translator=None):
if os.path.exists(extracted_usr_paths):
possible_paths.append(extracted_usr_paths)
# Find first existing path
for path in possible_paths:
if os.path.exists(path):
product_json_path = path
break
else:
raise OSError(translator.get('bypass.product_json_not_found') if translator else "product.json not found in common Linux paths")
error_msg = "product.json not found in common Linux paths"
logger.error(error_msg)
raise OSError(translator.get('bypass.product_json_not_found') if translator else error_msg)
else:
raise OSError(translator.get('bypass.unsupported_os', system=system) if translator else f"Unsupported operating system: {system}")
error_msg = f"Unsupported operating system: {system}"
logger.error(error_msg)
raise OSError(translator.get('bypass.unsupported_os', system=system) if translator else error_msg)
# Verify that the file exists
if not os.path.exists(product_json_path):
raise OSError(translator.get('bypass.file_not_found', path=product_json_path) if translator else f"File not found: {product_json_path}")
error_msg = f"File not found: {product_json_path}"
logger.error(error_msg)
raise OSError(translator.get('bypass.file_not_found', path=product_json_path) if translator else error_msg)
logger.info(f"Found product.json at: {product_json_path}")
return product_json_path
def compare_versions(version1, version2):
"""Compare two version strings"""
v1_parts = [int(x) for x in version1.split('.')]
v2_parts = [int(x) for x in version2.split('.')]
def compare_versions(version1: str, version2: str) -> int:
"""Compare two version strings.
for i in range(max(len(v1_parts), len(v2_parts))):
v1 = v1_parts[i] if i < len(v1_parts) else 0
v2 = v2_parts[i] if i < len(v2_parts) else 0
if v1 < v2:
Args:
version1: First version string (e.g., "0.48.7")
version2: Second version string (e.g., "0.46.0")
Returns:
int: -1 if version1 < version2, 0 if version1 == version2, 1 if version1 > version2
"""
try:
v1_parts = [int(x) for x in version1.split('.')]
v2_parts = [int(x) for x in version2.split('.')]
for i in range(max(len(v1_parts), len(v2_parts))):
v1 = v1_parts[i] if i < len(v1_parts) else 0
v2 = v2_parts[i] if i < len(v2_parts) else 0
if v1 < v2:
return -1
elif v1 > v2:
return 1
return 0
except (ValueError, TypeError) as e:
logger.warning(f"Error comparing versions {version1} and {version2}: {e}")
# Fall back to string comparison if numeric comparison fails
if version1 < version2:
return -1
elif v1 > v2:
elif version1 > version2:
return 1
return 0
else:
return 0
def bypass_version(translator=None):
"""Bypass Cursor version check by modifying product.json"""
def bypass_version(translator: Any = None) -> bool:
"""Bypass Cursor version check by modifying product.json.
Args:
translator: Optional translator for internationalization
Returns:
bool: True if the version was successfully bypassed, False otherwise
"""
try:
print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('bypass.starting') if translator else 'Starting Cursor version bypass...'}{Style.RESET_ALL}")
@@ -114,7 +172,12 @@ def bypass_version(translator=None):
try:
with open(product_json_path, "r", encoding="utf-8") as f:
product_data = json.load(f)
except json.JSONDecodeError as e:
logger.error(f"Invalid JSON in product.json: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.invalid_json', error=str(e)) if translator else f'Invalid JSON in product.json: {str(e)}'}{Style.RESET_ALL}")
return False
except Exception as e:
logger.error(f"Failed to read product.json: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.read_failed', error=str(e)) if translator else f'Failed to read product.json: {str(e)}'}{Style.RESET_ALL}")
return False
@@ -122,39 +185,98 @@ def bypass_version(translator=None):
current_version = product_data.get("version", "0.0.0")
print(f"{Fore.CYAN}{EMOJI['VERSION']} {translator.get('bypass.current_version', version=current_version) if translator else f'Current version: {current_version}'}{Style.RESET_ALL}")
# Target version to set
new_version = "0.48.7"
# Check if version needs to be modified
if compare_versions(current_version, "0.46.0") < 0:
# Create backup
timestamp = time.strftime("%Y%m%d%H%M%S")
backup_path = f"{product_json_path}.{timestamp}"
shutil.copy2(product_json_path, backup_path)
print(f"{Fore.GREEN}{EMOJI['BACKUP']} {translator.get('bypass.backup_created', path=backup_path) if translator else f'Backup created: {backup_path}'}{Style.RESET_ALL}")
try:
shutil.copy2(product_json_path, backup_path)
print(f"{Fore.GREEN}{EMOJI['BACKUP']} {translator.get('bypass.backup_created', path=backup_path) if translator else f'Backup created: {backup_path}'}{Style.RESET_ALL}")
except Exception as e:
logger.error(f"Failed to create backup: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.backup_failed', error=str(e)) if translator else f'Failed to create backup: {str(e)}'}{Style.RESET_ALL}")
return False
# Modify version
new_version = "0.48.7"
product_data["version"] = new_version
# Save modified product.json
try:
with open(product_json_path, "w", encoding="utf-8") as f:
json.dump(product_data, f, indent=2)
logger.info(f"Version updated from {current_version} to {new_version}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('bypass.version_updated', old=current_version, new=new_version) if translator else f'Version updated from {current_version} to {new_version}'}{Style.RESET_ALL}")
return True
except Exception as e:
logger.error(f"Failed to write product.json: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.write_failed', error=str(e)) if translator else f'Failed to write product.json: {str(e)}'}{Style.RESET_ALL}")
# Try to restore from backup if write fails
try:
if os.path.exists(backup_path):
shutil.copy2(backup_path, product_json_path)
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('bypass.restored_from_backup') if translator else 'Restored from backup'}{Style.RESET_ALL}")
except Exception as restore_error:
logger.error(f"Failed to restore from backup: {restore_error}")
return False
else:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('bypass.no_update_needed', version=current_version) if translator else f'No update needed. Current version {current_version} is already >= 0.46.0'}{Style.RESET_ALL}")
return True
except Exception as e:
logger.error(f"Version bypass failed: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('bypass.bypass_failed', error=str(e)) if translator else f'Version bypass failed: {str(e)}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('bypass.stack_trace') if translator else 'Stack trace'}: {traceback.format_exc()}{Style.RESET_ALL}")
return False
def main(translator=None):
"""Main function"""
return bypass_version(translator)
def run(translator: Any = None) -> bool:
"""Main function to run the version bypass.
Args:
translator: Optional translator for internationalization
Returns:
bool: True if successful, False otherwise
"""
try:
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['RESET']} {translator.get('bypass_version.title') if translator else 'Bypass Version Check'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
success = bypass_version(translator)
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
input(f"{EMOJI['INFO']} {translator.get('bypass_version.press_enter') if translator else 'Press Enter to continue...'}")
return success
except Exception as e:
logger.error(f"Error in run function: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} An unexpected error occurred: {str(e)}{Style.RESET_ALL}")
return False
def main(translator: Any = None) -> bool:
"""Entry point when called directly.
Args:
translator: Optional translator for internationalization
Returns:
bool: True if successful, False otherwise
"""
return run(translator)
if __name__ == "__main__":
main()
try:
from main import translator as main_translator
main(main_translator)
except ImportError:
logger.warning("Failed to import translator from main.py, running without translation")
main(None)
except Exception as e:
logger.error(f"Error running bypass_version.py: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} An unexpected error occurred: {str(e)}{Style.RESET_ALL}")

View File

@@ -4,10 +4,20 @@ import time
import hashlib
import base64
import struct
import logging
from colorama import Fore, Style, init
from typing import Optional, Dict, Union, Any, Tuple
# Initialize colorama
init()
init(autoreset=True)
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger(__name__)
# Define emoji constants
EMOJI = {
@@ -20,22 +30,57 @@ EMOJI = {
}
def generate_hashed64_hex(input_str: str, salt: str = '') -> str:
"""Generate a SHA-256 hash of input + salt and return as hex"""
"""Generate a SHA-256 hash of input + salt and return as hex.
Args:
input_str: The input string to hash
salt: Optional salt to add to the input string
Returns:
str: Hexadecimal representation of the hash
"""
if not input_str:
logger.warning("Empty input string provided for hashing")
return ""
hash_obj = hashlib.sha256()
hash_obj.update((input_str + salt).encode('utf-8'))
return hash_obj.hexdigest()
def obfuscate_bytes(byte_array: bytearray) -> bytearray:
"""Obfuscate bytes using the algorithm from utils.js"""
"""Obfuscate bytes using the algorithm from utils.js.
Args:
byte_array: The byte array to obfuscate
Returns:
bytearray: The obfuscated byte array
"""
if not byte_array:
return bytearray()
t = 165
for r in range(len(byte_array)):
byte_array[r] = ((byte_array[r] ^ t) + (r % 256)) & 0xFF
t = byte_array[r]
return byte_array
def generate_cursor_checksum(token: str, translator=None) -> str:
"""Generate Cursor checksum from token using the algorithm"""
def generate_cursor_checksum(token: str, translator: Any = None) -> str:
"""Generate Cursor checksum from token using the algorithm.
Args:
token: The authentication token
translator: Optional translator for internationalization
Returns:
str: The generated checksum
"""
try:
# Validate input
if not token or not isinstance(token, str):
logger.error("Invalid token provided")
return ""
# Clean the token
clean_token = token.strip()
@@ -54,15 +99,16 @@ def generate_cursor_checksum(token: str, translator=None) -> str:
# Combine final checksum
return f"{encoded_checksum}{machine_id}/{mac_machine_id}"
except Exception as e:
logger.error(f"Error generating checksum: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.error_generating_checksum', error=str(e)) if translator else f'Error generating checksum: {str(e)}'}{Style.RESET_ALL}")
return ""
def check_user_authorized(token: str, translator=None) -> bool:
def check_user_authorized(token: str, translator: Any = None) -> bool:
"""
Check if the user is authorized with the given token
Check if the user is authorized with the given token.
Args:
token (str): The authorization token
token: The authorization token
translator: Optional translator for internationalization
Returns:
@@ -71,6 +117,12 @@ def check_user_authorized(token: str, translator=None) -> bool:
try:
print(f"{Fore.CYAN}{EMOJI['CHECK']} {translator.get('auth_check.checking_authorization') if translator else 'Checking authorization...'}{Style.RESET_ALL}")
# Validate input
if not token or not isinstance(token, str):
logger.error("Invalid token provided")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.invalid_token') if translator else 'Invalid token'}{Style.RESET_ALL}")
return False
# Clean the token
if token and '%3A%3A' in token:
token = token.split('%3A%3A')[1]
@@ -81,6 +133,7 @@ def check_user_authorized(token: str, translator=None) -> bool:
token = token.strip()
if not token or len(token) < 10: # Add a basic validation for token length
logger.error("Token too short or empty after cleaning")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.invalid_token') if translator else 'Invalid token'}{Style.RESET_ALL}")
return False
@@ -90,7 +143,10 @@ def check_user_authorized(token: str, translator=None) -> bool:
try:
# Generate checksum
checksum = generate_cursor_checksum(token, translator)
if not checksum:
logger.error("Failed to generate checksum")
return False
# Create request headers
headers = {
'accept-encoding': 'gzip',
@@ -107,53 +163,137 @@ def check_user_authorized(token: str, translator=None) -> bool:
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.checking_usage_information') if translator else 'Checking usage information...'}{Style.RESET_ALL}")
# Make the request - this endpoint doesn't need a request body
usage_response = requests.post(
'https://api2.cursor.sh/aiserver.v1.DashboardService/GetUsageBasedPremiumRequests',
headers=headers,
data=b'', # Empty body
timeout=10
)
# Make the request with timeout and retry
max_retries = 3
for attempt in range(max_retries):
try:
usage_response = requests.post(
'https://api2.cursor.sh/aiserver.v1.DashboardService/GetUsageBasedPremiumRequests',
headers=headers,
data=b'', # Empty body
timeout=10
)
break
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e:
if attempt < max_retries - 1:
logger.warning(f"Request attempt {attempt + 1} failed: {e}. Retrying...")
time.sleep(2)
else:
raise
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.usage_response', response=usage_response.status_code) if translator else f'Usage response status: {usage_response.status_code}'}{Style.RESET_ALL}")
if usage_response.status_code == 200:
logger.info("User is authorized")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('auth_check.user_authorized') if translator else 'User is authorized'}{Style.RESET_ALL}")
return True
elif usage_response.status_code == 401 or usage_response.status_code == 403:
logger.warning("User is unauthorized")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.user_unauthorized') if translator else 'User is unauthorized'}{Style.RESET_ALL}")
return False
else:
logger.warning(f"Unexpected status code: {usage_response.status_code}")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.unexpected_status_code', code=usage_response.status_code) if translator else f'Unexpected status code: {usage_response.status_code}'}{Style.RESET_ALL}")
# If the token at least looks like a valid JWT, consider it valid
if token.startswith('eyJ') and '.' in token and len(token) > 100:
logger.info("Token appears to be in JWT format, but API check returned an unexpected status code")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.jwt_token_warning') if translator else 'Token appears to be in JWT format, but API check returned an unexpected status code. The token might be valid but API access is restricted.'}{Style.RESET_ALL}")
return True
return False
except requests.exceptions.Timeout:
logger.error("Request timed out")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.request_timeout') if translator else 'Request timed out'}{Style.RESET_ALL}")
# If the token at least looks like a valid JWT, consider it valid even if the API check fails
if token.startswith('eyJ') and '.' in token and len(token) > 100:
logger.info("Token appears to be in JWT format, but request timed out")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.jwt_token_warning') if translator else 'Token appears to be in JWT format, but API check timed out. The token might be valid but API access is restricted.'}{Style.RESET_ALL}")
return True
return False
except requests.exceptions.ConnectionError:
logger.error("Connection error")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.connection_error') if translator else 'Connection error'}{Style.RESET_ALL}")
# If the token at least looks like a valid JWT, consider it valid even if the API check fails
if token.startswith('eyJ') and '.' in token and len(token) > 100:
logger.info("Token appears to be in JWT format, but connection failed")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.jwt_token_warning') if translator else 'Token appears to be in JWT format, but API connection failed. The token might be valid but API access is restricted.'}{Style.RESET_ALL}")
return True
return False
except Exception as e:
logger.error(f"Error checking usage: {e}")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} Error checking usage: {str(e)}{Style.RESET_ALL}")
# If the token at least looks like a valid JWT, consider it valid even if the API check fails
if token.startswith('eyJ') and '.' in token and len(token) > 100:
logger.info("Token appears to be in JWT format, but API check failed")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.jwt_token_warning') if translator else 'Token appears to be in JWT format, but API check failed. The token might be valid but API access is restricted.'}{Style.RESET_ALL}")
return True
return False
except requests.exceptions.Timeout:
logger.error("Request timed out")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.request_timeout') if translator else 'Request timed out'}{Style.RESET_ALL}")
return False
except requests.exceptions.ConnectionError:
logger.error("Connection error")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.connection_error') if translator else 'Connection error'}{Style.RESET_ALL}")
return False
except Exception as e:
logger.error(f"Error checking authorization: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.check_error', error=str(e)) if translator else f'Error checking authorization: {str(e)}'}{Style.RESET_ALL}")
return False
def run(translator=None):
"""Run function to be called from main.py"""
def get_token_from_database(translator: Any = None) -> str:
"""
Get token from database using cursor_acc_info.py.
Args:
translator: Optional translator for internationalization
Returns:
str: The token if found, empty string otherwise
"""
try:
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.getting_token_from_db') if translator else 'Getting token from database...'}{Style.RESET_ALL}")
# Import functions from cursor_acc_info.py
from cursor_acc_info import get_token
# Get token using the get_token function
token = get_token()
if token:
logger.info("Token found in database")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('auth_check.token_found_in_db') if translator else 'Token found in database'}{Style.RESET_ALL}")
return token
else:
logger.warning("Token not found in database")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.token_not_found_in_db') if translator else 'Token not found in database'}{Style.RESET_ALL}")
return ""
except ImportError:
logger.error("cursor_acc_info.py not found")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.cursor_acc_info_not_found') if translator else 'cursor_acc_info.py not found'}{Style.RESET_ALL}")
return ""
except Exception as e:
logger.error(f"Error getting token from database: {e}")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.error_getting_token_from_db', error=str(e)) if translator else f'Error getting token from database: {str(e)}'}{Style.RESET_ALL}")
return ""
def run(translator: Any = None) -> bool:
"""Run function to be called from main.py.
Args:
translator: Optional translator for internationalization
Returns:
bool: True if authorization successful, False otherwise
"""
try:
# Ask user if they want to get token from database or input manually
choice = input(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.token_source') if translator else 'Get token from database or input manually? (d/m, default: d): '}{Style.RESET_ALL}").strip().lower()
@@ -162,23 +302,7 @@ def run(translator=None):
# If user chooses database or default
if not choice or choice == 'd':
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('auth_check.getting_token_from_db') if translator else 'Getting token from database...'}{Style.RESET_ALL}")
try:
# Import functions from cursor_acc_info.py
from cursor_acc_info import get_token
# Get token using the get_token function
token = get_token()
if token:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('auth_check.token_found_in_db') if translator else 'Token found in database'}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.token_not_found_in_db') if translator else 'Token not found in database'}{Style.RESET_ALL}")
except ImportError:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.cursor_acc_info_not_found') if translator else 'cursor_acc_info.py not found'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.error_getting_token_from_db', error=str(e)) if translator else f'Error getting token from database: {str(e)}'}{Style.RESET_ALL}")
token = get_token_from_database(translator)
# If token not found in database or user chooses manual input
if not token:
@@ -193,22 +317,50 @@ def run(translator=None):
is_authorized = check_user_authorized(token, translator)
if is_authorized:
logger.info("Authorization successful")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('auth_check.authorization_successful') if translator else 'Authorization successful!'}{Style.RESET_ALL}")
else:
logger.warning("Authorization failed")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.authorization_failed') if translator else 'Authorization failed!'}{Style.RESET_ALL}")
return is_authorized
except KeyboardInterrupt:
print(f"\n{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('auth_check.operation_cancelled') if translator else 'Operation cancelled by user'}{Style.RESET_ALL}")
return False
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('auth_check.unexpected_error', error=str(e)) if translator else f'Unexpected error: {str(e)}'}{Style.RESET_ALL}")
logger.error(f"Error in run function: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} An unexpected error occurred: {str(e)}{Style.RESET_ALL}")
return False
def main(translator=None):
"""Main function to check user authorization"""
return run(translator)
def main(translator: Any = None) -> bool:
"""Main function to be called when script is run directly.
Args:
translator: Optional translator for internationalization
Returns:
bool: True if authorization successful, False otherwise
"""
try:
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['CHECK']} {translator.get('auth_check.title') if translator else 'Check User Authorization'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
result = run(translator)
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
input(f"{EMOJI['INFO']} {translator.get('auth_check.press_enter') if translator else 'Press Enter to continue...'}")
return result
except Exception as e:
logger.error(f"Error in main function: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} An unexpected error occurred: {str(e)}{Style.RESET_ALL}")
return False
if __name__ == "__main__":
main()
try:
from main import translator as main_translator
main(main_translator)
except ImportError:
logger.warning("Failed to import translator from main.py, running without translation")
main(None)
except Exception as e:
logger.error(f"Error running check_user_authorized.py: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} An unexpected error occurred: {str(e)}{Style.RESET_ALL}")

763
config.py
View File

@@ -1,11 +1,29 @@
import os
import sys
import configparser
from colorama import Fore, Style
from utils import get_user_documents_path, get_linux_cursor_path, get_default_driver_path, get_default_browser_path
import shutil
import logging
import tempfile
import datetime
import platform
from pathlib import Path
from typing import Optional, Dict, Any, Union, List, Tuple
from colorama import Fore, Style, init
# Initialize colorama
init(autoreset=True)
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger(__name__)
# Import utils after logging setup to avoid circular imports
from utils import get_user_documents_path, get_linux_cursor_path, get_default_driver_path, get_default_browser_path
# Define emoji constants
EMOJI = {
"INFO": "",
"WARNING": "⚠️",
@@ -15,45 +33,103 @@ EMOJI = {
"ARROW": "➡️",
"USER": "👤",
"KEY": "🔑",
"SETTINGS": "⚙️"
"SETTINGS": "⚙️",
"CONFIG": "📝"
}
# global config cache
# Global config cache
_config_cache = None
def setup_config(translator=None):
"""Setup configuration file and return config object"""
try:
# get documents path
class ConfigManager:
"""Class to manage configuration operations"""
def __init__(self, translator: Any = None):
"""Initialize ConfigManager
Args:
translator: Optional translator for internationalization
"""
self.translator = translator
self.config = configparser.ConfigParser()
self.config_dir = None
self.config_file = None
def _get_message(self, key: str, fallback: str, **kwargs) -> str:
"""Get translated message or fallback
Args:
key: Translation key
fallback: Fallback message if translation not available
**kwargs: Format parameters for the message
Returns:
str: Translated or fallback message
"""
if self.translator:
return self.translator.get(key, fallback=fallback, **kwargs)
return fallback.format(**kwargs) if kwargs else fallback
def setup_config_directory(self) -> Tuple[str, str]:
"""Setup configuration directory
Returns:
Tuple[str, str]: Configuration directory and file paths
"""
# Get documents path
docs_path = get_user_documents_path()
if not docs_path or not os.path.exists(docs_path):
# if documents path not found, use current directory
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.documents_path_not_found', fallback='Documents path not found, using current directory') if translator else 'Documents path not found, using current directory'}{Style.RESET_ALL}")
# If documents path not found, use current directory
msg = self._get_message('config.documents_path_not_found',
'Documents path not found, using current directory')
logger.warning(msg)
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {msg}{Style.RESET_ALL}")
docs_path = os.path.abspath('.')
# normalize path
# Normalize path
config_dir = os.path.normpath(os.path.join(docs_path, ".cursor-free-vip"))
config_file = os.path.normpath(os.path.join(config_dir, "config.ini"))
# create config directory, only print message when directory not exists
# Create config directory
dir_exists = os.path.exists(config_dir)
try:
os.makedirs(config_dir, exist_ok=True)
if not dir_exists: # only print message when directory not exists
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.config_dir_created', path=config_dir) if translator else f'Config directory created: {config_dir}'}{Style.RESET_ALL}")
if not dir_exists:
msg = self._get_message('config.config_dir_created',
'Config directory created: {path}', path=config_dir)
logger.info(f"Config directory created: {config_dir}")
print(f"{Fore.CYAN}{EMOJI['INFO']} {msg}{Style.RESET_ALL}")
except Exception as e:
# if cannot create directory, use temporary directory
import tempfile
# If cannot create directory, use temporary directory
logger.warning(f"Failed to create config directory: {e}")
temp_dir = os.path.normpath(os.path.join(tempfile.gettempdir(), ".cursor-free-vip"))
temp_exists = os.path.exists(temp_dir)
config_dir = temp_dir
config_file = os.path.normpath(os.path.join(config_dir, "config.ini"))
os.makedirs(config_dir, exist_ok=True)
if not temp_exists: # only print message when temporary directory not exists
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.using_temp_dir', path=config_dir, error=str(e)) if translator else f'Using temporary directory due to error: {config_dir} (Error: {str(e)})'}{Style.RESET_ALL}")
try:
os.makedirs(config_dir, exist_ok=True)
if not temp_exists:
msg = self._get_message('config.using_temp_dir',
'Using temporary directory due to error: {path} (Error: {error})',
path=config_dir, error=str(e))
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {msg}{Style.RESET_ALL}")
except Exception as inner_e:
logger.error(f"Failed to create temporary config directory: {inner_e}")
# Last resort: use current directory
config_dir = os.path.abspath('.')
config_file = os.path.join(config_dir, "config.ini")
self.config_dir = config_dir
self.config_file = config_file
return config_dir, config_file
def get_default_config(self) -> Dict[str, Dict[str, Any]]:
"""Get default configuration
# create config object
config = configparser.ConfigParser()
Returns:
Dict[str, Dict[str, Any]]: Default configuration dictionary
"""
config_dir = self.config_dir or os.path.join(get_user_documents_path(), ".cursor-free-vip")
# Default configuration
default_config = {
@@ -70,7 +146,7 @@ def setup_config(translator=None):
'opera_path': get_default_browser_path('opera'),
'opera_driver_path': get_default_driver_path('opera'),
'operagx_path': get_default_browser_path('operagx'),
'operagx_driver_path': get_default_driver_path('chrome') # Opera GX 使用 Chrome 驱动
'operagx_driver_path': get_default_driver_path('chrome') # Opera GX uses Chrome driver
},
'Turnstile': {
'handle_turnstile_time': '2',
@@ -98,13 +174,13 @@ def setup_config(translator=None):
'enabled_account_info': 'True'
},
'OAuth': {
'show_selection_alert': False, # 默认不显示选择提示弹窗
'timeout': 120,
'max_attempts': 3
'show_selection_alert': 'False',
'timeout': '120',
'max_attempts': '3'
},
'Token': {
'refresh_server': 'https://token.cursorpro.com.cn',
'enable_refresh': True
'enable_refresh': 'True'
},
'Language': {
'current_language': '', # Set by local system detection if empty
@@ -115,266 +191,411 @@ def setup_config(translator=None):
}
# Add system-specific path configuration
self._add_system_paths(default_config)
return default_config
def _add_system_paths(self, default_config: Dict[str, Dict[str, Any]]) -> None:
"""Add system-specific paths to the default configuration
Args:
default_config: Default configuration dictionary to update
"""
if sys.platform == "win32":
appdata = os.getenv("APPDATA")
localappdata = os.getenv("LOCALAPPDATA", "")
default_config['WindowsPaths'] = {
'storage_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "storage.json"),
'sqlite_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "state.vscdb"),
'machine_id_path': os.path.join(appdata, "Cursor", "machineId"),
'cursor_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app"),
'updater_path': os.path.join(localappdata, "cursor-updater"),
'update_yml_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app-update.yml"),
'product_json_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app", "product.json")
}
# Create storage directory
os.makedirs(os.path.dirname(default_config['WindowsPaths']['storage_path']), exist_ok=True)
self._add_windows_paths(default_config)
elif sys.platform == "darwin":
default_config['MacPaths'] = {
'storage_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/storage.json")),
'sqlite_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/state.vscdb")),
'machine_id_path': os.path.expanduser("~/Library/Application Support/Cursor/machineId"),
'cursor_path': "/Applications/Cursor.app/Contents/Resources/app",
'updater_path': os.path.expanduser("~/Library/Application Support/cursor-updater"),
'update_yml_path': "/Applications/Cursor.app/Contents/Resources/app-update.yml",
'product_json_path': "/Applications/Cursor.app/Contents/Resources/app/product.json"
}
# Create storage directory
os.makedirs(os.path.dirname(default_config['MacPaths']['storage_path']), exist_ok=True)
self._add_macos_paths(default_config)
elif sys.platform == "linux":
# Get the actual user's home directory, handling both sudo and normal cases
sudo_user = os.environ.get('SUDO_USER')
current_user = sudo_user if sudo_user else (os.getenv('USER') or os.getenv('USERNAME'))
if not current_user:
current_user = os.path.expanduser('~').split('/')[-1]
# Handle sudo case
if sudo_user:
actual_home = f"/home/{sudo_user}"
root_home = "/root"
else:
actual_home = f"/home/{current_user}"
root_home = None
if not os.path.exists(actual_home):
actual_home = os.path.expanduser("~")
# Define base config directory
config_base = os.path.join(actual_home, ".config")
# Try both "Cursor" and "cursor" directory names in both user and root locations
cursor_dir = None
possible_paths = [
os.path.join(config_base, "Cursor"),
os.path.join(config_base, "cursor"),
os.path.join(root_home, ".config", "Cursor") if root_home else None,
os.path.join(root_home, ".config", "cursor") if root_home else None
]
for path in possible_paths:
if path and os.path.exists(path):
cursor_dir = path
break
if not cursor_dir:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.neither_cursor_nor_cursor_directory_found', config_base=config_base) if translator else f'Neither Cursor nor cursor directory found in {config_base}'}{Style.RESET_ALL}")
if root_home:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.also_checked', path=f'{root_home}/.config') if translator else f'Also checked {root_home}/.config'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.please_make_sure_cursor_is_installed_and_has_been_run_at_least_once') if translator else 'Please make sure Cursor is installed and has been run at least once'}{Style.RESET_ALL}")
# Define Linux paths using the found cursor directory
storage_path = os.path.abspath(os.path.join(cursor_dir, "User/globalStorage/storage.json")) if cursor_dir else ""
storage_dir = os.path.dirname(storage_path) if storage_path else ""
# Verify paths and permissions
try:
# Check storage directory
if storage_dir and not os.path.exists(storage_dir):
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.storage_directory_not_found', storage_dir=storage_dir) if translator else f'Storage directory not found: {storage_dir}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.please_make_sure_cursor_is_installed_and_has_been_run_at_least_once') if translator else 'Please make sure Cursor is installed and has been run at least once'}{Style.RESET_ALL}")
# Check storage.json with more detailed verification
if storage_path and os.path.exists(storage_path):
# Get file stats
try:
stat = os.stat(storage_path)
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.storage_file_found', storage_path=storage_path) if translator else f'Storage file found: {storage_path}'}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_size', size=stat.st_size) if translator else f'File size: {stat.st_size} bytes'}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_permissions', permissions=oct(stat.st_mode & 0o777)) if translator else f'File permissions: {oct(stat.st_mode & 0o777)}'}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_owner', owner=stat.st_uid) if translator else f'File owner: {stat.st_uid}'}{Style.RESET_ALL}")
print(f"{Fore.GREEN}{EMOJI['INFO']} {translator.get('config.file_group', group=stat.st_gid) if translator else f'File group: {stat.st_gid}'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.error_getting_file_stats', error=str(e)) if translator else f'Error getting file stats: {str(e)}'}{Style.RESET_ALL}")
# Check if file is readable and writable
if not os.access(storage_path, os.R_OK | os.W_OK):
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.permission_denied', storage_path=storage_path) if translator else f'Permission denied: {storage_path}'}{Style.RESET_ALL}")
if sudo_user:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.try_running', command=f'chown {sudo_user}:{sudo_user} {storage_path}') if translator else f'Try running: chown {sudo_user}:{sudo_user} {storage_path}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.and') if translator else 'And'}: chmod 644 {storage_path}{Style.RESET_ALL}")
else:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.try_running', command=f'chown {current_user}:{current_user} {storage_path}') if translator else f'Try running: chown {current_user}:{current_user} {storage_path}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.and') if translator else 'And'}: chmod 644 {storage_path}{Style.RESET_ALL}")
# Try to read the file to verify it's not corrupted
try:
with open(storage_path, 'r') as f:
content = f.read()
if not content.strip():
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.storage_file_is_empty', storage_path=storage_path) if translator else f'Storage file is empty: {storage_path}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.the_file_might_be_corrupted_please_reinstall_cursor') if translator else 'The file might be corrupted, please reinstall Cursor'}{Style.RESET_ALL}")
else:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('config.storage_file_is_valid_and_contains_data') if translator else 'Storage file is valid and contains data'}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.error_reading_storage_file', error=str(e)) if translator else f'Error reading storage file: {str(e)}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.the_file_might_be_corrupted_please_reinstall_cursor') if translator else 'The file might be corrupted. Please reinstall Cursor'}{Style.RESET_ALL}")
elif storage_path:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.storage_file_not_found', storage_path=storage_path) if translator else f'Storage file not found: {storage_path}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.please_make_sure_cursor_is_installed_and_has_been_run_at_least_once') if translator else 'Please make sure Cursor is installed and has been run at least once'}{Style.RESET_ALL}")
except (OSError, IOError) as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.error_checking_linux_paths', error=str(e)) if translator else f'Error checking Linux paths: {str(e)}'}{Style.RESET_ALL}")
# Define all paths using the found cursor directory
default_config['LinuxPaths'] = {
'storage_path': storage_path,
'sqlite_path': os.path.abspath(os.path.join(cursor_dir, "User/globalStorage/state.vscdb")) if cursor_dir else "",
'machine_id_path': os.path.join(cursor_dir, "machineid") if cursor_dir else "",
'cursor_path': get_linux_cursor_path(),
'updater_path': os.path.join(config_base, "cursor-updater"),
'update_yml_path': os.path.join(cursor_dir, "resources/app-update.yml") if cursor_dir else "",
'product_json_path': os.path.join(cursor_dir, "resources/app/product.json") if cursor_dir else ""
}
# Add tempmail_plus configuration
default_config['TempMailPlus'] = {
'enabled': 'false',
'email': '',
'epin': ''
}
# Read existing configuration and merge
if os.path.exists(config_file):
config.read(config_file, encoding='utf-8')
config_modified = False
for section, options in default_config.items():
if not config.has_section(section):
config.add_section(section)
config_modified = True
for option, value in options.items():
if not config.has_option(section, option):
config.set(section, option, str(value))
config_modified = True
if translator:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('config.config_option_added', option=f'{section}.{option}') if translator else f'Config option added: {section}.{option}'}{Style.RESET_ALL}")
if config_modified:
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
if translator:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('config.config_updated') if translator else 'Config updated'}{Style.RESET_ALL}")
else:
for section, options in default_config.items():
config.add_section(section)
for option, value in options.items():
config.set(section, option, str(value))
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
if translator:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('config.config_created', config_file=config_file) if translator else f'Config created: {config_file}'}{Style.RESET_ALL}")
return config
except Exception as e:
if translator:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.config_setup_error', error=str(e)) if translator else f'Error setting up config: {str(e)}'}{Style.RESET_ALL}")
return None
self._add_linux_paths(default_config)
def print_config(config, translator=None):
"""Print configuration in a readable format"""
def _add_windows_paths(self, default_config: Dict[str, Dict[str, Any]]) -> None:
"""Add Windows-specific paths to the default configuration
Args:
default_config: Default configuration dictionary to update
"""
appdata = os.getenv("APPDATA", "")
localappdata = os.getenv("LOCALAPPDATA", "")
if not appdata or not localappdata:
logger.warning("APPDATA or LOCALAPPDATA environment variables not found")
appdata = os.path.expanduser("~\\AppData\\Roaming")
localappdata = os.path.expanduser("~\\AppData\\Local")
default_config['WindowsPaths'] = {
'storage_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "storage.json"),
'sqlite_path': os.path.join(appdata, "Cursor", "User", "globalStorage", "state.vscdb"),
'machine_id_path': os.path.join(appdata, "Cursor", "machineId"),
'cursor_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app"),
'updater_path': os.path.join(localappdata, "cursor-updater"),
'update_yml_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app-update.yml"),
'product_json_path': os.path.join(localappdata, "Programs", "Cursor", "resources", "app", "product.json")
}
# Create storage directory
try:
storage_dir = os.path.dirname(default_config['WindowsPaths']['storage_path'])
os.makedirs(storage_dir, exist_ok=True)
except Exception as e:
logger.warning(f"Failed to create storage directory: {e}")
def _add_macos_paths(self, default_config: Dict[str, Dict[str, Any]]) -> None:
"""Add macOS-specific paths to the default configuration
Args:
default_config: Default configuration dictionary to update
"""
default_config['MacPaths'] = {
'storage_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/storage.json")),
'sqlite_path': os.path.abspath(os.path.expanduser("~/Library/Application Support/Cursor/User/globalStorage/state.vscdb")),
'machine_id_path': os.path.expanduser("~/Library/Application Support/Cursor/machineId"),
'cursor_path': "/Applications/Cursor.app/Contents/Resources/app",
'updater_path': os.path.expanduser("~/Library/Application Support/cursor-updater"),
'update_yml_path': "/Applications/Cursor.app/Contents/Resources/app-update.yml",
'product_json_path': "/Applications/Cursor.app/Contents/Resources/app/product.json"
}
# Create storage directory
try:
storage_dir = os.path.dirname(default_config['MacPaths']['storage_path'])
os.makedirs(storage_dir, exist_ok=True)
except Exception as e:
logger.warning(f"Failed to create storage directory: {e}")
def _add_linux_paths(self, default_config: Dict[str, Dict[str, Any]]) -> None:
"""Add Linux-specific paths to the default configuration
Args:
default_config: Default configuration dictionary to update
"""
# Get the actual user's home directory, handling both sudo and normal cases
sudo_user = os.environ.get('SUDO_USER')
current_user = sudo_user if sudo_user else (os.getenv('USER') or os.getenv('USERNAME'))
if not current_user:
current_user = os.path.expanduser('~').split('/')[-1]
# Handle sudo case
if sudo_user:
actual_home = f"/home/{sudo_user}"
root_home = "/root"
else:
actual_home = f"/home/{current_user}"
root_home = None
if not os.path.exists(actual_home):
actual_home = os.path.expanduser("~")
# Define base config directory
config_base = os.path.join(actual_home, ".config")
# Try both "Cursor" and "cursor" directory names in both user and root locations
cursor_dir = None
possible_paths = [
os.path.join(config_base, "Cursor"),
os.path.join(config_base, "cursor"),
os.path.join(root_home, ".config", "Cursor") if root_home else None,
os.path.join(root_home, ".config", "cursor") if root_home else None
]
for path in possible_paths:
if path and os.path.exists(path):
cursor_dir = path
break
if not cursor_dir:
msg = self._get_message('config.neither_cursor_nor_cursor_directory_found',
'Neither Cursor nor cursor directory found in {config_base}',
config_base=config_base)
logger.warning(f"Cursor directory not found in {config_base}")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {msg}{Style.RESET_ALL}")
if root_home:
msg = self._get_message('config.also_checked',
'Also checked {path}',
path=f'{root_home}/.config')
print(f"{Fore.YELLOW}{EMOJI['INFO']} {msg}{Style.RESET_ALL}")
msg = self._get_message('config.please_make_sure_cursor_is_installed_and_has_been_run_at_least_once',
'Please make sure Cursor is installed and has been run at least once')
print(f"{Fore.YELLOW}{EMOJI['INFO']} {msg}{Style.RESET_ALL}")
# Define Linux paths using the found cursor directory
storage_path = os.path.abspath(os.path.join(cursor_dir, "User/globalStorage/storage.json")) if cursor_dir else ""
storage_dir = os.path.dirname(storage_path) if storage_path else ""
# Set default Linux paths
default_config['LinuxPaths'] = {
'storage_path': storage_path,
'sqlite_path': os.path.abspath(os.path.join(cursor_dir, "User/globalStorage/state.vscdb")) if cursor_dir else "",
'machine_id_path': os.path.join(cursor_dir, "machineid") if cursor_dir else "",
'cursor_path': get_linux_cursor_path(),
'updater_path': os.path.join(config_base, "cursor-updater"),
'update_yml_path': os.path.join(cursor_dir, "resources/app-update.yml") if cursor_dir else "",
'product_json_path': os.path.join(cursor_dir, "resources/app/product.json") if cursor_dir else ""
}
# Verify paths and permissions
self._verify_linux_paths(storage_path, storage_dir)
def _verify_linux_paths(self, storage_path: str, storage_dir: str) -> None:
"""Verify Linux paths and permissions
Args:
storage_path: Path to the storage.json file
storage_dir: Directory containing the storage.json file
"""
try:
# Check storage directory
if storage_dir and not os.path.exists(storage_dir):
msg = self._get_message('config.storage_directory_not_found',
'Storage directory not found: {storage_dir}',
storage_dir=storage_dir)
logger.warning(f"Storage directory not found: {storage_dir}")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {msg}{Style.RESET_ALL}")
msg = self._get_message('config.please_make_sure_cursor_is_installed_and_has_been_run_at_least_once',
'Please make sure Cursor is installed and has been run at least once')
print(f"{Fore.YELLOW}{EMOJI['INFO']} {msg}{Style.RESET_ALL}")
# Check storage.json with more detailed verification
if storage_path and os.path.exists(storage_path):
# Get file stats
try:
stat = os.stat(storage_path)
msg = self._get_message('config.storage_file_found',
'Storage file found: {storage_path}',
storage_path=storage_path)
print(f"{Fore.GREEN}{EMOJI['INFO']} {msg}{Style.RESET_ALL}")
# Log file details
file_details = [
('config.file_size', 'File size: {size} bytes', {'size': stat.st_size}),
('config.file_permissions', 'File permissions: {permissions}', {'permissions': oct(stat.st_mode & 0o777)}),
('config.file_owner', 'File owner: {owner}', {'owner': stat.st_uid}),
('config.file_group', 'File group: {group}', {'group': stat.st_gid})
]
for key, fallback, kwargs in file_details:
msg = self._get_message(key, fallback, **kwargs)
print(f"{Fore.GREEN}{EMOJI['INFO']} {msg}{Style.RESET_ALL}")
except Exception as e:
logger.error(f"Error getting file stats: {e}")
msg = self._get_message('config.error_getting_file_stats',
'Error getting file stats: {error}',
error=str(e))
print(f"{Fore.RED}{EMOJI['ERROR']} {msg}{Style.RESET_ALL}")
# Check if file is readable and writable
if not os.access(storage_path, os.R_OK | os.W_OK):
msg = self._get_message('config.permission_denied',
'Permission denied: {storage_path}',
storage_path=storage_path)
logger.warning(f"Permission denied: {storage_path}")
print(f"{Fore.RED}{EMOJI['ERROR']} {msg}{Style.RESET_ALL}")
sudo_user = os.environ.get('SUDO_USER')
current_user = sudo_user if sudo_user else (os.getenv('USER') or os.getenv('USERNAME'))
if sudo_user:
cmd = f"chown {sudo_user}:{sudo_user} {storage_path}"
else:
cmd = f"chown {current_user}:{current_user} {storage_path}"
msg = self._get_message('config.try_running',
'Try running: {command}',
command=cmd)
print(f"{Fore.YELLOW}{EMOJI['INFO']} {msg}{Style.RESET_ALL}")
msg = self._get_message('config.and', 'And')
print(f"{Fore.YELLOW}{EMOJI['INFO']} {msg}: chmod 644 {storage_path}{Style.RESET_ALL}")
# Try to read the file to verify it's not corrupted
try:
with open(storage_path, 'r') as f:
content = f.read()
if not content.strip():
msg = self._get_message('config.storage_file_is_empty',
'Storage file is empty: {storage_path}',
storage_path=storage_path)
logger.warning(f"Storage file is empty: {storage_path}")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {msg}{Style.RESET_ALL}")
msg = self._get_message('config.the_file_might_be_corrupted_please_reinstall_cursor',
'The file might be corrupted, please reinstall Cursor')
print(f"{Fore.YELLOW}{EMOJI['INFO']} {msg}{Style.RESET_ALL}")
else:
msg = self._get_message('config.storage_file_is_valid_and_contains_data',
'Storage file is valid and contains data')
logger.info("Storage file is valid and contains data")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {msg}{Style.RESET_ALL}")
except Exception as e:
logger.error(f"Error reading storage file: {e}")
msg = self._get_message('config.error_reading_storage_file',
'Error reading storage file: {error}',
error=str(e))
print(f"{Fore.RED}{EMOJI['ERROR']} {msg}{Style.RESET_ALL}")
msg = self._get_message('config.the_file_might_be_corrupted_please_reinstall_cursor',
'The file might be corrupted. Please reinstall Cursor')
print(f"{Fore.YELLOW}{EMOJI['INFO']} {msg}{Style.RESET_ALL}")
elif storage_path:
msg = self._get_message('config.storage_file_not_found',
'Storage file not found: {storage_path}',
storage_path=storage_path)
logger.warning(f"Storage file not found: {storage_path}")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {msg}{Style.RESET_ALL}")
msg = self._get_message('config.please_make_sure_cursor_is_installed_and_has_been_run_at_least_once',
'Please make sure Cursor is installed and has been run at least once')
print(f"{Fore.YELLOW}{EMOJI['INFO']} {msg}{Style.RESET_ALL}")
except (OSError, IOError) as e:
logger.error(f"Error checking Linux paths: {e}")
msg = self._get_message('config.error_checking_linux_paths',
'Error checking Linux paths: {error}',
error=str(e))
print(f"{Fore.RED}{EMOJI['ERROR']} {msg}{Style.RESET_ALL}")
def setup(self) -> configparser.ConfigParser:
"""Setup configuration
Returns:
configparser.ConfigParser: Configured ConfigParser object
"""
# Setup config directory
self.setup_config_directory()
# Get default configuration
default_config = self.get_default_config()
# Read existing config if it exists
if os.path.exists(self.config_file):
try:
self.config.read(self.config_file)
logger.info(f"Read existing configuration from {self.config_file}")
except Exception as e:
logger.error(f"Error reading config file: {e}")
# Continue with default config
# Update config with default values for missing sections/options
for section, options in default_config.items():
if not self.config.has_section(section):
self.config.add_section(section)
for option, value in options.items():
if not self.config.has_option(section, option):
self.config.set(section, option, str(value))
# Save config
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
self.config.write(f)
logger.info(f"Configuration saved to {self.config_file}")
except Exception as e:
logger.error(f"Error saving config file: {e}")
return self.config
def setup_config(translator: Any = None) -> configparser.ConfigParser:
"""Setup configuration file and return config object
Args:
translator: Optional translator for internationalization
Returns:
configparser.ConfigParser: Configured ConfigParser object
"""
try:
config_manager = ConfigManager(translator)
return config_manager.setup()
except Exception as e:
logger.error(f"Error setting up configuration: {e}")
# Return empty config as fallback
return configparser.ConfigParser()
def print_config(config: configparser.ConfigParser, translator: Any = None) -> None:
"""Print configuration
Args:
config: ConfigParser object
translator: Optional translator for internationalization
"""
if not config:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('config.config_not_available') if translator else 'Configuration not available'}{Style.RESET_ALL}")
print(f"{Fore.RED}{EMOJI['ERROR']} Configuration not available{Style.RESET_ALL}")
return
print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.configuration') if translator else 'Configuration'}:{Style.RESET_ALL}")
print(f"\n{Fore.CYAN}{'' * 70}{Style.RESET_ALL}")
for section in config.sections():
print(f"{Fore.GREEN}[{section}]{Style.RESET_ALL}")
for key, value in config.items(section):
# 对布尔值进行特殊处理,使其显示为彩色
if value.lower() in ('true', 'yes', 'on', '1'):
value_display = f"{Fore.GREEN}{translator.get('config.enabled') if translator else 'Enabled'}{Style.RESET_ALL}"
elif value.lower() in ('false', 'no', 'off', '0'):
value_display = f"{Fore.RED}{translator.get('config.disabled') if translator else 'Disabled'}{Style.RESET_ALL}"
else:
value_display = value
print(f" {key} = {value_display}")
print(f"\n{Fore.CYAN}{EMOJI['CONFIG']} Configuration:{Style.RESET_ALL}")
print(f"\n{Fore.CYAN}{'' * 70}{Style.RESET_ALL}")
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip", "config.ini")
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.config_directory') if translator else 'Config Directory'}: {config_dir}{Style.RESET_ALL}")
for section in config.sections():
print(f"\n{Fore.CYAN}[{section}]{Style.RESET_ALL}")
for option in config.options(section):
value = config.get(section, option)
# Mask sensitive information
if 'token' in option.lower() or 'password' in option.lower() or 'key' in option.lower():
value = '*' * 8
print(f" {option} = {value}")
print()
def force_update_config(translator=None):
"""
Force update configuration file with latest defaults if update check is enabled.
def force_update_config(translator: Any = None) -> configparser.ConfigParser:
"""Force update configuration
Args:
translator: Translator instance
translator: Optional translator for internationalization
Returns:
ConfigParser instance or None if failed
configparser.ConfigParser: Updated ConfigParser object
"""
global _config_cache
_config_cache = None
# Create backup of existing config
try:
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
current_time = datetime.datetime.now()
# If the config file exists, check if forced update is enabled
if os.path.exists(config_file):
# First, read the existing configuration
existing_config = configparser.ConfigParser()
existing_config.read(config_file, encoding='utf-8')
# Check if "enabled_update_check" is True
update_enabled = True # Default to True if not set
if existing_config.has_section('Utils') and existing_config.has_option('Utils', 'enabled_force_update'):
update_enabled = existing_config.get('Utils', 'enabled_force_update').strip().lower() in ('true', 'yes', '1', 'on')
if update_enabled:
try:
# Create a backup
backup_file = f"{config_file}.bak.{current_time.strftime('%Y%m%d_%H%M%S')}"
shutil.copy2(config_file, backup_file)
if translator:
print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.backup_created', path=backup_file) if translator else f'Backup created: {backup_file}'}{Style.RESET_ALL}")
print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.config_force_update_enabled') if translator else 'Config file force update enabled'}{Style.RESET_ALL}")
# Delete the original config file (forced update)
os.remove(config_file)
if translator:
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.config_removed') if translator else 'Config file removed for forced update'}{Style.RESET_ALL}")
except Exception as e:
if translator:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.backup_failed', error=str(e)) if translator else f'Failed to backup config: {str(e)}'}{Style.RESET_ALL}")
else:
if translator:
print(f"\n{Fore.CYAN}{EMOJI['INFO']} {translator.get('config.config_force_update_disabled', fallback='Config file force update disabled by configuration. Keeping existing config file.') if translator else 'Config file force update disabled by configuration. Keeping existing config file.'}{Style.RESET_ALL}")
# Generate a new (or updated) configuration if needed
return setup_config(translator)
timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
backup_file = f"{config_file}.{timestamp}.bak"
import shutil
shutil.copy2(config_file, backup_file)
msg = translator.get('config.backup_created', fallback='Backup created: {path}', path=backup_file) if translator else f"Backup created: {backup_file}"
logger.info(f"Config backup created: {backup_file}")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {msg}{Style.RESET_ALL}")
except Exception as e:
if translator:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('config.force_update_failed', error=str(e)) if translator else f'Force update config failed: {str(e)}'}{Style.RESET_ALL}")
return None
logger.error(f"Error creating config backup: {e}")
# Setup new config
return setup_config(translator)
def get_config(translator=None):
"""Get existing config or create new one"""
def get_config(translator: Any = None) -> configparser.ConfigParser:
"""Get configuration
Args:
translator: Optional translator for internationalization
Returns:
configparser.ConfigParser: ConfigParser object
"""
global _config_cache
if _config_cache is None:
_config_cache = setup_config(translator)
return _config_cache
if _config_cache is not None:
return _config_cache
try:
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
if os.path.exists(config_file):
config = configparser.ConfigParser()
config.read(config_file)
_config_cache = config
return config
else:
_config_cache = setup_config(translator)
return _config_cache
except Exception as e:
logger.error(f"Error getting configuration: {e}")
return configparser.ConfigParser()

475
enhanced_config.py Normal file
View File

@@ -0,0 +1,475 @@
import os
import sys
import configparser
import logging
import tempfile
import datetime
import platform
import json
import shutil
from pathlib import Path
from typing import Optional, Dict, Any, Union, List, Tuple, TypeVar, Generic
from dataclasses import dataclass, field
from enum import Enum
from colorama import Fore, Style, init
import yaml
# Initialize colorama
init(autoreset=True)
# Configure enhanced logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
handlers=[
logging.FileHandler("cursor_free_vip.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# Enhanced emoji constants
EMOJI = {
"INFO": "",
"WARNING": "⚠️",
"ERROR": "",
"SUCCESS": "",
"ADMIN": "🔒",
"ARROW": "➡️",
"USER": "👤",
"KEY": "🔑",
"SETTINGS": "⚙️",
"CONFIG": "📝",
"VALIDATION": "🔍",
"BACKUP": "💾",
"RESTORE": "🔄",
"SECURITY": "🔐",
"PERFORMANCE": ""
}
class ConfigFormat(Enum):
INI = "ini"
JSON = "json"
YAML = "yaml"
class ValidationError(Exception):
pass
@dataclass
class BrowserConfig:
default_browser: str = "chrome"
chrome_path: str = ""
chrome_driver_path: str = ""
edge_path: str = ""
edge_driver_path: str = ""
firefox_path: str = ""
firefox_driver_path: str = ""
brave_path: str = ""
brave_driver_path: str = ""
opera_path: str = ""
opera_driver_path: str = ""
operagx_path: str = ""
operagx_driver_path: str = ""
@dataclass
class TimingConfig:
min_random_time: float = 0.1
max_random_time: float = 0.8
page_load_wait: str = "0.1-0.8"
input_wait: str = "0.3-0.8"
submit_wait: str = "0.5-1.5"
verification_code_input: str = "0.1-0.3"
verification_success_wait: str = "2-3"
verification_retry_wait: str = "2-3"
email_check_initial_wait: str = "4-6"
email_refresh_wait: str = "2-4"
settings_page_load_wait: str = "1-2"
failed_retry_time: str = "0.5-1"
retry_interval: str = "8-12"
max_timeout: int = 160
@dataclass
class SecurityConfig:
enable_encryption: bool = True
encryption_key: str = ""
enable_backup: bool = True
backup_retention_days: int = 30
enable_audit_log: bool = True
sensitive_fields: List[str] = field(default_factory=lambda: ["password", "token", "key"])
class EnhancedConfigManager:
def __init__(self, translator: Any = None, config_format: ConfigFormat = ConfigFormat.INI):
self.translator = translator
self.config_format = config_format
self.config_dir = None
self.config_file = None
self.backup_dir = None
self.audit_log_file = None
self._config_cache = {}
self._validation_schema = self._load_validation_schema()
def _get_message(self, key: str, fallback: str, **kwargs) -> str:
"""Get translated message or fallback with enhanced error handling"""
try:
if self.translator:
return self.translator.get(key, fallback=fallback, **kwargs)
except Exception as e:
logger.warning(f"Translation error for key '{key}': {e}")
return fallback.format(**kwargs) if kwargs else fallback
def _load_validation_schema(self) -> Dict[str, Any]:
"""Load configuration validation schema"""
return {
"Browser": {
"default_browser": {"type": "str", "allowed": ["chrome", "edge", "firefox", "brave", "opera", "operagx"]},
"chrome_path": {"type": "str", "required": False},
"chrome_driver_path": {"type": "str", "required": False}
},
"Timing": {
"min_random_time": {"type": "float", "min": 0.0, "max": 10.0},
"max_random_time": {"type": "float", "min": 0.0, "max": 10.0},
"max_timeout": {"type": "int", "min": 10, "max": 600}
},
"Security": {
"enable_encryption": {"type": "bool"},
"backup_retention_days": {"type": "int", "min": 1, "max": 365}
}
}
def setup_config_directory(self) -> Tuple[str, str]:
"""Setup configuration directory with enhanced error handling"""
try:
# Get documents path with fallback
docs_path = self._get_documents_path()
config_dir = os.path.normpath(os.path.join(docs_path, ".cursor-free-vip"))
config_file = os.path.normpath(os.path.join(config_dir, self._get_config_filename()))
# Create directory structure
self._create_directory_structure(config_dir)
self.config_dir = config_dir
self.config_file = config_file
self.backup_dir = os.path.join(config_dir, "backups")
self.audit_log_file = os.path.join(config_dir, "audit.log")
return config_dir, config_file
except Exception as e:
logger.error(f"Failed to setup config directory: {e}")
raise ValidationError(f"Configuration setup failed: {e}")
def _get_documents_path(self) -> str:
"""Get documents path with enhanced platform detection"""
try:
if platform.system() == "Windows":
import winreg
with winreg.OpenKey(winreg.HKEY_CURRENT_USER,
"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders") as key:
documents_path, _ = winreg.QueryValueEx(key, "Personal")
return documents_path
elif platform.system() == "Darwin": # macOS
return os.path.expanduser("~/Documents")
else: # Linux
# Try XDG user directories
xdg_config = os.path.expanduser("~/.config/user-dirs.dirs")
if os.path.exists(xdg_config):
with open(xdg_config, "r") as f:
for line in f:
if line.startswith("XDG_DOCUMENTS_DIR"):
path = line.split("=")[1].strip().strip('"').replace("$HOME", os.path.expanduser("~"))
if os.path.exists(path):
return path
return os.path.expanduser("~/Documents")
except Exception as e:
logger.warning(f"Failed to get documents path: {e}")
return os.path.abspath('.')
def _get_config_filename(self) -> str:
"""Get configuration filename based on format"""
format_extensions = {
ConfigFormat.INI: "config.ini",
ConfigFormat.JSON: "config.json",
ConfigFormat.YAML: "config.yaml"
}
return format_extensions.get(self.config_format, "config.ini")
def _create_directory_structure(self, config_dir: str) -> None:
"""Create directory structure with proper permissions"""
try:
os.makedirs(config_dir, exist_ok=True)
# Create subdirectories
subdirs = ["backups", "logs", "cache", "temp"]
for subdir in subdirs:
subdir_path = os.path.join(config_dir, subdir)
os.makedirs(subdir_path, exist_ok=True)
# Set proper permissions on Unix systems
if platform.system() != "Windows":
os.chmod(config_dir, 0o700)
except Exception as e:
logger.error(f"Failed to create directory structure: {e}")
raise
def validate_config(self, config_data: Dict[str, Any]) -> List[str]:
"""Validate configuration data against schema"""
errors = []
for section, section_schema in self._validation_schema.items():
if section not in config_data:
continue
section_data = config_data[section]
for key, validation in section_schema.items():
if key not in section_data:
if validation.get("required", False):
errors.append(f"Missing required field: {section}.{key}")
continue
value = section_data[key]
value_type = validation.get("type")
# Type validation
if value_type == "str" and not isinstance(value, str):
errors.append(f"Invalid type for {section}.{key}: expected str, got {type(value)}")
elif value_type == "int" and not isinstance(value, int):
errors.append(f"Invalid type for {section}.{key}: expected int, got {type(value)}")
elif value_type == "float" and not isinstance(value, (int, float)):
errors.append(f"Invalid type for {section}.{key}: expected float, got {type(value)}")
elif value_type == "bool" and not isinstance(value, bool):
errors.append(f"Invalid type for {section}.{key}: expected bool, got {type(value)}")
# Range validation
if "min" in validation and value < validation["min"]:
errors.append(f"Value too small for {section}.{key}: {value} < {validation['min']}")
if "max" in validation and value > validation["max"]:
errors.append(f"Value too large for {section}.{key}: {value} > {validation['max']}")
# Allowed values validation
if "allowed" in validation and value not in validation["allowed"]:
errors.append(f"Invalid value for {section}.{key}: {value} not in {validation['allowed']}")
return errors
def get_default_config(self) -> Dict[str, Any]:
"""Get enhanced default configuration"""
from utils import get_default_browser_path, get_default_driver_path
config_dir = self.config_dir or os.path.join(self._get_documents_path(), ".cursor-free-vip")
default_config = {
'Browser': {
'default_browser': 'chrome',
'chrome_path': get_default_browser_path('chrome'),
'chrome_driver_path': get_default_driver_path('chrome'),
'edge_path': get_default_browser_path('edge'),
'edge_driver_path': get_default_driver_path('edge'),
'firefox_path': get_default_browser_path('firefox'),
'firefox_driver_path': get_default_driver_path('firefox'),
'brave_path': get_default_browser_path('brave'),
'brave_driver_path': get_default_driver_path('brave'),
'opera_path': get_default_browser_path('opera'),
'opera_driver_path': get_default_driver_path('opera'),
'operagx_path': get_default_browser_path('operagx'),
'operagx_driver_path': get_default_driver_path('chrome')
},
'Timing': {
'min_random_time': 0.1,
'max_random_time': 0.8,
'page_load_wait': '0.1-0.8',
'input_wait': '0.3-0.8',
'submit_wait': '0.5-1.5',
'verification_code_input': '0.1-0.3',
'verification_success_wait': '2-3',
'verification_retry_wait': '2-3',
'email_check_initial_wait': '4-6',
'email_refresh_wait': '2-4',
'settings_page_load_wait': '1-2',
'failed_retry_time': '0.5-1',
'retry_interval': '8-12',
'max_timeout': 160
},
'Security': {
'enable_encryption': True,
'encryption_key': '',
'enable_backup': True,
'backup_retention_days': 30,
'enable_audit_log': True,
'sensitive_fields': ['password', 'token', 'key', 'secret']
},
'Performance': {
'enable_caching': True,
'cache_ttl': 3600,
'max_concurrent_operations': 5,
'enable_compression': True
},
'Logging': {
'log_level': 'INFO',
'log_file': 'cursor_free_vip.log',
'max_log_size': 10485760, # 10MB
'log_rotation': 5
}
}
# Add system-specific paths
self._add_system_paths(default_config)
return default_config
def _add_system_paths(self, config: Dict[str, Any]) -> None:
system = platform.system()
if system == "Windows":
self._add_windows_paths(config)
elif system == "Darwin":
self._add_macos_paths(config)
else:
self._add_linux_paths(config)
def _add_windows_paths(self, config: Dict[str, Any]) -> None:
username = os.getenv('USERNAME', 'user')
config['WindowsPaths'] = {
'storage_path': f"C:\\Users\\{username}\\AppData\\Roaming\\Cursor\\User\\globalStorage\\storage.json",
'sqlite_path': f"C:\\Users\\{username}\\AppData\\Roaming\\Cursor\\User\\globalStorage\\state.vscdb",
'machine_id_path': f"C:\\Users\\{username}\\AppData\\Roaming\\Cursor\\machineId",
'cursor_path': f"C:\\Users\\{username}\\AppData\\Local\\Programs\\Cursor\\resources\\app",
'updater_path': f"C:\\Users\\{username}\\AppData\\Local\\cursor-updater",
'update_yml_path': f"C:\\Users\\{username}\\AppData\\Local\\Programs\\Cursor\\resources\\app-update.yml",
'product_json_path': f"C:\\Users\\{username}\\AppData\\Local\\Programs\\Cursor\\resources\\app\\product.json"
}
def _add_macos_paths(self, config: Dict[str, Any]) -> None:
username = os.getenv('USER', 'user')
config['MacOSPaths'] = {
'storage_path': f"/Users/{username}/Library/Application Support/Cursor/User/globalStorage/storage.json",
'sqlite_path': f"/Users/{username}/Library/Application Support/Cursor/User/globalStorage/state.vscdb",
'machine_id_path': f"/Users/{username}/Library/Application Support/Cursor/machineId",
'cursor_path': f"/Applications/Cursor.app/Contents/Resources/app",
'updater_path': f"/Users/{username}/Library/Application Support/cursor-updater",
'update_yml_path': f"/Applications/Cursor.app/Contents/Resources/app-update.yml",
'product_json_path': f"/Applications/Cursor.app/Contents/Resources/app/product.json"
}
def _add_linux_paths(self, config: Dict[str, Any]) -> None:
username = os.getenv('USER', 'user')
config['LinuxPaths'] = {
'storage_path': f"/home/{username}/.config/Cursor/User/globalStorage/storage.json",
'sqlite_path': f"/home/{username}/.config/Cursor/User/globalStorage/state.vscdb",
'machine_id_path': f"/home/{username}/.config/Cursor/machineid",
'cursor_path': "/opt/Cursor/resources/app",
'updater_path': f"/home/{username}/.config/cursor-updater",
'update_yml_path': "/opt/Cursor/resources/app-update.yml",
'product_json_path': "/opt/Cursor/resources/app/product.json"
}
def save_config(self, config_data: Dict[str, Any], backup: bool = True) -> None:
try:
errors = self.validate_config(config_data)
if errors:
raise ValidationError(f"Configuration validation failed:\n" + "\n".join(errors))
if backup and self.backup_dir:
self._create_backup()
if self.config_format == ConfigFormat.JSON:
self._save_json_config(config_data)
elif self.config_format == ConfigFormat.YAML:
self._save_yaml_config(config_data)
else:
self._save_ini_config(config_data)
self._log_config_change("Configuration saved successfully")
except Exception as e:
logger.error(f"Failed to save configuration: {e}")
raise
def _create_backup(self) -> None:
try:
if not self.config_file or not os.path.exists(self.config_file):
return
if not self.backup_dir:
return
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
backup_filename = f"config_backup_{timestamp}.{self.config_format.value}"
backup_path = os.path.join(self.backup_dir, backup_filename)
shutil.copy2(self.config_file, backup_path)
self._cleanup_old_backups()
logger.info(f"Configuration backup created: {backup_path}")
except Exception as e:
logger.warning(f"Failed to create backup: {e}")
def _cleanup_old_backups(self) -> None:
try:
if not self.backup_dir:
return
retention_days = 30
cutoff_time = datetime.datetime.now() - datetime.timedelta(days=retention_days)
for filename in os.listdir(self.backup_dir):
if filename.startswith("config_backup_"):
file_path = os.path.join(self.backup_dir, filename)
file_time = datetime.datetime.fromtimestamp(os.path.getctime(file_path))
if file_time < cutoff_time:
os.remove(file_path)
logger.info(f"Removed old backup: {filename}")
except Exception as e:
logger.warning(f"Failed to cleanup old backups: {e}")
def _save_json_config(self, config_data: Dict[str, Any]) -> None:
if self.config_file:
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(config_data, f, indent=2, ensure_ascii=False)
def _save_yaml_config(self, config_data: Dict[str, Any]) -> None:
if self.config_file:
with open(self.config_file, 'w', encoding='utf-8') as f:
yaml.dump(config_data, f, default_flow_style=False, allow_unicode=True)
def _save_ini_config(self, config_data: Dict[str, Any]) -> None:
config = configparser.ConfigParser()
for section, items in config_data.items():
config.add_section(section)
for key, value in items.items():
config.set(section, key, str(value))
if self.config_file:
with open(self.config_file, 'w', encoding='utf-8') as f:
config.write(f)
def _log_config_change(self, message: str) -> None:
try:
if self.audit_log_file:
timestamp = datetime.datetime.now().isoformat()
log_entry = f"{timestamp} - {message}\n"
with open(self.audit_log_file, 'a', encoding='utf-8') as f:
f.write(log_entry)
except Exception as e:
logger.warning(f"Failed to log configuration change: {e}")
def create_config_manager(translator: Any = None, format_type: str = "ini") -> EnhancedConfigManager:
format_map = {
"ini": ConfigFormat.INI,
"json": ConfigFormat.JSON,
"yaml": ConfigFormat.YAML
}
config_format = format_map.get(format_type.lower(), ConfigFormat.INI)
return EnhancedConfigManager(translator, config_format)

351
enhanced_error_handler.py Normal file
View File

@@ -0,0 +1,351 @@
import os
import sys
import logging
import traceback
import json
import time
from typing import Optional, Dict, Any, Callable, List, Union
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
import threading
from contextlib import contextmanager
class ErrorSeverity(Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
CRITICAL = "critical"
class ErrorCategory(Enum):
CONFIGURATION = "configuration"
NETWORK = "network"
PROCESS = "process"
FILE_SYSTEM = "file_system"
PERMISSION = "permission"
VALIDATION = "validation"
BROWSER = "browser"
AUTHENTICATION = "authentication"
SYSTEM = "system"
UNKNOWN = "unknown"
@dataclass
class ErrorInfo:
timestamp: float
category: ErrorCategory
severity: ErrorSeverity
message: str
exception: Optional[Exception] = None
traceback: Optional[str] = None
context: Dict[str, Any] = field(default_factory=dict)
user_action: Optional[str] = None
resolved: bool = False
resolution_time: Optional[float] = None
class ErrorRecoveryStrategy:
def __init__(self, max_retries: int = 3, backoff_factor: float = 2.0):
self.max_retries = max_retries
self.backoff_factor = backoff_factor
self.retry_counts = {}
def should_retry(self, error_info: ErrorInfo) -> bool:
"""Determine if operation should be retried"""
error_key = f"{error_info.category.value}_{error_info.message}"
current_retries = self.retry_counts.get(error_key, 0)
if current_retries >= self.max_retries:
return False
# Don't retry critical errors
if error_info.severity == ErrorSeverity.CRITICAL:
return False
# Don't retry permission errors
if error_info.category == ErrorCategory.PERMISSION:
return False
return True
def get_retry_delay(self, error_info: ErrorInfo) -> float:
"""Get delay before next retry"""
error_key = f"{error_info.category.value}_{error_info.message}"
current_retries = self.retry_counts.get(error_key, 0)
return (self.backoff_factor ** current_retries)
def record_retry(self, error_info: ErrorInfo) -> None:
"""Record a retry attempt"""
error_key = f"{error_info.category.value}_{error_info.message}"
self.retry_counts[error_key] = self.retry_counts.get(error_key, 0) + 1
class EnhancedErrorHandler:
"""Enhanced error handler with logging, categorization, and recovery"""
def __init__(self, log_file: Optional[str] = None, enable_recovery: bool = True):
self.log_file = log_file or "cursor_free_vip_errors.log"
self.enable_recovery = enable_recovery
self.recovery_strategy = ErrorRecoveryStrategy()
self.error_history: List[ErrorInfo] = []
self.error_callbacks: Dict[ErrorCategory, List[Callable]] = {}
self._setup_logging()
self._lock = threading.Lock()
def _setup_logging(self) -> None:
"""Setup enhanced logging"""
# Create logs directory if it doesn't exist
log_dir = os.path.dirname(self.log_file)
if log_dir:
os.makedirs(log_dir, exist_ok=True)
# Configure file handler
file_handler = logging.FileHandler(self.log_file, encoding='utf-8')
file_handler.setLevel(logging.DEBUG)
# Configure console handler
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# Create formatter
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)
# Configure logger
self.logger = logging.getLogger('CursorFreeVIP.ErrorHandler')
self.logger.setLevel(logging.DEBUG)
self.logger.addHandler(file_handler)
self.logger.addHandler(console_handler)
def categorize_error(self, exception: Exception, context: Optional[Dict[str, Any]] = None) -> ErrorCategory:
"""Categorize error based on exception type and context"""
exception_type = type(exception).__name__
exception_message = str(exception).lower()
# Network errors
if any(keyword in exception_message for keyword in ['connection', 'timeout', 'network', 'http', 'url']):
return ErrorCategory.NETWORK
# File system errors
if any(keyword in exception_message for keyword in ['file', 'directory', 'path', 'not found', 'permission']):
if 'permission' in exception_message:
return ErrorCategory.PERMISSION
return ErrorCategory.FILE_SYSTEM
# Process errors
if any(keyword in exception_message for keyword in ['process', 'pid', 'terminate', 'kill']):
return ErrorCategory.PROCESS
# Browser errors
if any(keyword in exception_message for keyword in ['browser', 'driver', 'selenium', 'chrome', 'firefox']):
return ErrorCategory.BROWSER
# Authentication errors
if any(keyword in exception_message for keyword in ['auth', 'login', 'token', 'credential']):
return ErrorCategory.AUTHENTICATION
# Configuration errors
if any(keyword in exception_message for keyword in ['config', 'setting', 'parameter']):
return ErrorCategory.CONFIGURATION
# Validation errors
if any(keyword in exception_message for keyword in ['validation', 'invalid', 'format']):
return ErrorCategory.VALIDATION
# System errors
if any(keyword in exception_message for keyword in ['system', 'os', 'platform']):
return ErrorCategory.SYSTEM
return ErrorCategory.UNKNOWN
def determine_severity(self, exception: Exception, category: ErrorCategory) -> ErrorSeverity:
"""Determine error severity"""
exception_message = str(exception).lower()
# Critical errors
if any(keyword in exception_message for keyword in ['fatal', 'critical', 'corrupt', 'broken']):
return ErrorSeverity.CRITICAL
# High severity errors
if category in [ErrorCategory.PERMISSION, ErrorCategory.AUTHENTICATION]:
return ErrorSeverity.HIGH
if any(keyword in exception_message for keyword in ['access denied', 'unauthorized', 'forbidden']):
return ErrorSeverity.HIGH
# Medium severity errors
if category in [ErrorCategory.NETWORK, ErrorCategory.FILE_SYSTEM, ErrorCategory.PROCESS]:
return ErrorSeverity.MEDIUM
# Low severity errors
if category in [ErrorCategory.VALIDATION, ErrorCategory.CONFIGURATION]:
return ErrorSeverity.LOW
return ErrorSeverity.MEDIUM
def handle_error(self, exception: Exception, context: Optional[Dict[str, Any]] = None,
user_action: Optional[str] = None) -> ErrorInfo:
with self._lock:
category = self.categorize_error(exception, context)
severity = self.determine_severity(exception, category)
error_info = ErrorInfo(
timestamp=time.time(),
category=category,
severity=severity,
message=str(exception),
exception=exception,
traceback=traceback.format_exc(),
context=context or {},
user_action=user_action
)
self._log_error(error_info)
self.error_history.append(error_info)
self._execute_callbacks(error_info)
if self.enable_recovery:
self._attempt_recovery(error_info)
return error_info
def _log_error(self, error_info: ErrorInfo) -> None:
log_message = f"""
Error Details:
- Category: {error_info.category.value}
- Severity: {error_info.severity.value}
- Message: {error_info.message}
- Timestamp: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(error_info.timestamp))}
- User Action: {error_info.user_action or 'N/A'}
- Context: {json.dumps(error_info.context, indent=2)}
"""
if error_info.traceback:
log_message += f"\nTraceback:\n{error_info.traceback}"
if error_info.severity == ErrorSeverity.CRITICAL:
self.logger.critical(log_message)
elif error_info.severity == ErrorSeverity.HIGH:
self.logger.error(log_message)
elif error_info.severity == ErrorSeverity.MEDIUM:
self.logger.warning(log_message)
else:
self.logger.info(log_message)
def _execute_callbacks(self, error_info: ErrorInfo) -> None:
callbacks = self.error_callbacks.get(error_info.category, [])
for callback in callbacks:
try:
callback(error_info)
except Exception as e:
self.logger.error(f"Error in callback: {e}")
def _attempt_recovery(self, error_info: ErrorInfo) -> None:
if not self.recovery_strategy.should_retry(error_info):
return
delay = self.recovery_strategy.get_retry_delay(error_info)
self.logger.info(f"Attempting recovery in {delay:.2f} seconds...")
self.recovery_strategy.record_retry(error_info)
threading.Timer(delay, self._retry_operation, args=[error_info]).start()
def _retry_operation(self, error_info: ErrorInfo) -> None:
self.logger.info(f"Retrying operation for error: {error_info.message}")
def register_callback(self, category: ErrorCategory, callback: Callable[[ErrorInfo], None]) -> None:
if category not in self.error_callbacks:
self.error_callbacks[category] = []
self.error_callbacks[category].append(callback)
def get_error_summary(self, hours: int = 24) -> Dict[str, Any]:
cutoff_time = time.time() - (hours * 3600)
recent_errors = [e for e in self.error_history if e.timestamp >= cutoff_time]
summary = {
'total_errors': len(recent_errors),
'by_category': {},
'by_severity': {},
'most_common': [],
'unresolved': len([e for e in recent_errors if not e.resolved])
}
for error in recent_errors:
category = error.category.value
severity = error.severity.value
summary['by_category'][category] = summary['by_category'].get(category, 0) + 1
summary['by_severity'][severity] = summary['by_severity'].get(severity, 0) + 1
error_messages = [e.message for e in recent_errors]
from collections import Counter
message_counts = Counter(error_messages)
summary['most_common'] = message_counts.most_common(5)
return summary
def resolve_error(self, error_info: ErrorInfo, resolution: str) -> None:
error_info.resolved = True
error_info.resolution_time = time.time()
error_info.user_action = resolution
self.logger.info(f"Error resolved: {error_info.message} - Resolution: {resolution}")
def clear_history(self, older_than_hours: int = 168) -> None:
cutoff_time = time.time() - (older_than_hours * 3600)
with self._lock:
self.error_history = [e for e in self.error_history if e.timestamp >= cutoff_time]
self.logger.info(f"Cleared error history older than {older_than_hours} hours")
@contextmanager
def error_context(handler: EnhancedErrorHandler, context: Optional[Dict[str, Any]] = None,
user_action: Optional[str] = None):
try:
yield
except Exception as e:
handler.handle_error(e, context, user_action)
raise
error_handler = EnhancedErrorHandler()
def handle_error(exception: Exception, context: Optional[Dict[str, Any]] = None,
user_action: Optional[str] = None) -> ErrorInfo:
return error_handler.handle_error(exception, context, user_action)
def safe_execute(func: Callable, *args, context: Optional[Dict[str, Any]] = None,
user_action: Optional[str] = None, **kwargs) -> Any:
try:
return func(*args, **kwargs)
except Exception as e:
error_handler.handle_error(e, context, user_action)
raise
def retry_on_error(func: Callable, max_retries: int = 3, *args,
context: Optional[Dict[str, Any]] = None, user_action: Optional[str] = None, **kwargs) -> Any:
last_exception: Optional[Exception] = None
for attempt in range(max_retries):
try:
return func(*args, **kwargs)
except Exception as e:
last_exception = e
error_info = error_handler.handle_error(e, context, user_action)
if attempt < max_retries - 1:
delay = 2 ** attempt
time.sleep(delay)
continue
else:
break
if last_exception is not None:
raise last_exception
else:
raise RuntimeError("Unexpected error in retry_on_error")

677
enhanced_utils.py Normal file
View File

@@ -0,0 +1,677 @@
import os
import sys
import platform
import random
import shutil
import logging
import subprocess
import threading
import time
import hashlib
import json
from typing import Optional, Dict, List, Union, Tuple, Any, Callable
from pathlib import Path
from dataclasses import dataclass
from enum import Enum
import psutil
import requests
from concurrent.futures import ThreadPoolExecutor, as_completed
# Configure enhanced logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger(__name__)
class ProcessStatus(Enum):
RUNNING = "running"
STOPPED = "stopped"
NOT_FOUND = "not_found"
ERROR = "error"
@dataclass
class ProcessInfo:
pid: int
name: str
status: ProcessStatus
memory_usage: float = 0.0
cpu_usage: float = 0.0
start_time: float = 0.0
command_line: str = ""
@dataclass
class SystemInfo:
platform: str
architecture: str
python_version: str
total_memory: float
available_memory: float
cpu_count: int
disk_usage: Dict[str, float]
class EnhancedPathManager:
def __init__(self):
self._path_cache = {}
self._executable_cache = {}
self._browser_cache = {}
def get_user_documents_path(self) -> str:
"""Get user documents path with enhanced error handling"""
cache_key = "documents_path"
if cache_key in self._path_cache:
return self._path_cache[cache_key]
try:
if platform.system() == "Windows":
path = self._get_windows_documents_path()
elif platform.system() == "Darwin":
path = self._get_macos_documents_path()
else:
path = self._get_linux_documents_path()
# Validate path exists
if not os.path.exists(path):
logger.warning(f"Documents path does not exist: {path}")
path = os.path.expanduser("~/Documents")
self._path_cache[cache_key] = path
return path
except Exception as e:
logger.error(f"Failed to get documents path: {e}")
fallback = os.path.expanduser("~/Documents")
self._path_cache[cache_key] = fallback
return fallback
def _get_windows_documents_path(self) -> str:
"""Get Windows documents path from registry"""
try:
import winreg
with winreg.OpenKey(winreg.HKEY_CURRENT_USER,
"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders") as key:
documents_path, _ = winreg.QueryValueEx(key, "Personal")
return documents_path
except Exception as e:
logger.warning(f"Failed to get Windows documents path from registry: {e}")
return os.path.expanduser("~\\Documents")
def _get_macos_documents_path(self) -> str:
"""Get macOS documents path"""
return os.path.expanduser("~/Documents")
def _get_linux_documents_path(self) -> str:
"""Get Linux documents path with XDG support"""
try:
xdg_config = os.path.expanduser("~/.config/user-dirs.dirs")
if os.path.exists(xdg_config):
with open(xdg_config, "r") as f:
for line in f:
if line.startswith("XDG_DOCUMENTS_DIR"):
path = line.split("=")[1].strip().strip('"').replace("$HOME", os.path.expanduser("~"))
if os.path.exists(path):
return path
except Exception as e:
logger.warning(f"Failed to read XDG config: {e}")
return os.path.expanduser("~/Documents")
def find_executable(self, executable_names: List[str], validate: bool = True) -> Optional[str]:
"""Find executable with enhanced validation"""
cache_key = tuple(sorted(executable_names))
if cache_key in self._executable_cache:
return self._executable_cache[cache_key]
for name in executable_names:
try:
path = shutil.which(name)
if path and (not validate or self._validate_executable(path)):
self._executable_cache[cache_key] = path
return path
except Exception as e:
logger.debug(f"Failed to find executable {name}: {e}")
continue
self._executable_cache[cache_key] = None
return None
def _validate_executable(self, path: str) -> bool:
"""Validate executable file"""
try:
if not os.path.exists(path):
return False
# Check if file is executable
if platform.system() != "Windows":
if not os.access(path, os.X_OK):
return False
# Check file size (should not be 0)
if os.path.getsize(path) == 0:
return False
return True
except Exception as e:
logger.debug(f"Executable validation failed for {path}: {e}")
return False
class EnhancedBrowserManager:
"""Enhanced browser management with automatic detection and validation"""
def __init__(self):
self.path_manager = EnhancedPathManager()
self._browser_paths = {}
self._driver_paths = {}
def get_browser_path(self, browser_type: str) -> str:
"""Get browser path with enhanced detection"""
browser_type = browser_type.lower()
if browser_type in self._browser_paths:
return self._browser_paths[browser_type]
try:
if platform.system() == "Windows":
path = self._get_windows_browser_path(browser_type)
elif platform.system() == "Darwin":
path = self._get_macos_browser_path(browser_type)
else:
path = self._get_linux_browser_path(browser_type)
self._browser_paths[browser_type] = path
return path
except Exception as e:
logger.error(f"Failed to get browser path for {browser_type}: {e}")
return ""
def get_driver_path(self, browser_type: str) -> str:
"""Get driver path with enhanced detection"""
browser_type = browser_type.lower()
if browser_type in self._driver_paths:
return self._driver_paths[browser_type]
try:
# Map browser types to driver types
driver_map = {
'chrome': 'chromedriver',
'edge': 'msedgedriver',
'firefox': 'geckodriver',
'brave': 'chromedriver',
'opera': 'chromedriver',
'operagx': 'chromedriver'
}
driver_name = driver_map.get(browser_type, 'chromedriver')
path = self._find_driver_path(driver_name)
self._driver_paths[browser_type] = path
return path
except Exception as e:
logger.error(f"Failed to get driver path for {browser_type}: {e}")
return ""
def _get_windows_browser_path(self, browser_type: str) -> str:
"""Get Windows browser path"""
browser_paths = {
'chrome': [
shutil.which("chrome"),
r"C:\Program Files\Google\Chrome\Application\chrome.exe",
r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Google', 'Chrome', 'Application', 'chrome.exe')
],
'edge': [
shutil.which("msedge"),
r"C:\Program Files\Microsoft\Edge\Application\msedge.exe",
r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
],
'firefox': [
shutil.which("firefox"),
r"C:\Program Files\Mozilla Firefox\firefox.exe",
r"C:\Program Files (x86)\Mozilla Firefox\firefox.exe"
],
'opera': [
shutil.which("opera"),
r"C:\Program Files\Opera\opera.exe",
r"C:\Program Files (x86)\Opera\opera.exe",
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera', 'launcher.exe')
],
'operagx': [
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera GX', 'launcher.exe'),
r"C:\Program Files\Opera GX\opera.exe",
r"C:\Program Files (x86)\Opera GX\opera.exe"
],
'brave': [
shutil.which("brave"),
os.path.join(os.environ.get('PROGRAMFILES', ''), 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
os.path.join(os.environ.get('PROGRAMFILES(X86)', ''), 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe')
]
}
paths = browser_paths.get(browser_type, [])
for path in paths:
if path and os.path.exists(path):
return path
return ""
def _get_macos_browser_path(self, browser_type: str) -> str:
"""Get macOS browser path"""
browser_paths = {
'chrome': [
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"~/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
],
'edge': [
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
"~/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"
],
'firefox': [
"/Applications/Firefox.app/Contents/MacOS/firefox",
"~/Applications/Firefox.app/Contents/MacOS/firefox"
],
'opera': [
"/Applications/Opera.app/Contents/MacOS/Opera",
"~/Applications/Opera.app/Contents/MacOS/Opera"
],
'operagx': [
"/Applications/Opera GX.app/Contents/MacOS/Opera GX",
"~/Applications/Opera GX.app/Contents/MacOS/Opera GX"
],
'brave': [
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
"~/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
]
}
paths = browser_paths.get(browser_type, [])
for path in paths:
expanded_path = os.path.expanduser(path)
if os.path.exists(expanded_path):
return expanded_path
return ""
def _get_linux_browser_path(self, browser_type: str) -> str:
"""Get Linux browser path"""
browser_names = {
'chrome': ['google-chrome', 'chrome', 'chromium-browser', 'chromium'],
'edge': ['microsoft-edge', 'msedge', 'edge'],
'firefox': ['firefox', 'firefox-esr'],
'opera': ['opera', 'opera-stable'],
'operagx': ['opera-gx'],
'brave': ['brave-browser', 'brave']
}
names = browser_names.get(browser_type, [browser_type])
return self.path_manager.find_executable(names) or ""
def _find_driver_path(self, driver_name: str) -> str:
"""Find driver executable path"""
# Try to find in PATH first
path = self.path_manager.find_executable([driver_name])
if path:
return path
# Try common installation paths
if platform.system() == "Windows":
driver_paths = [
os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", f"{driver_name}.exe"),
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'WebDriver', f"{driver_name}.exe"),
f"C:\\Program Files\\{driver_name}\\{driver_name}.exe"
]
elif platform.system() == "Darwin":
driver_paths = [
os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", driver_name),
f"/usr/local/bin/{driver_name}",
f"/opt/homebrew/bin/{driver_name}"
]
else:
driver_paths = [
os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", driver_name),
f"/usr/local/bin/{driver_name}",
f"/usr/bin/{driver_name}"
]
for driver_path in driver_paths:
if os.path.exists(driver_path):
return driver_path
return ""
class EnhancedProcessManager:
"""Enhanced process management with monitoring and control"""
def __init__(self):
self._process_cache = {}
self._monitoring_threads = {}
def find_cursor_processes(self) -> List[ProcessInfo]:
"""Find all Cursor processes with detailed information"""
processes = []
try:
for proc in psutil.process_iter(['pid', 'name', 'cmdline', 'memory_info', 'cpu_percent', 'create_time']):
try:
if self._is_cursor_process(proc.info['name']):
process_info = ProcessInfo(
pid=proc.info['pid'],
name=proc.info['name'],
status=ProcessStatus.RUNNING,
memory_usage=proc.info['memory_info'].rss / 1024 / 1024, # MB
cpu_usage=proc.info['cpu_percent'],
start_time=proc.info['create_time'],
command_line=' '.join(proc.info['cmdline']) if proc.info['cmdline'] else ''
)
processes.append(process_info)
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
except Exception as e:
logger.error(f"Failed to find Cursor processes: {e}")
return processes
def _is_cursor_process(self, process_name: str) -> bool:
"""Check if process is related to Cursor"""
cursor_names = ['cursor', 'Cursor', 'CURSOR']
return any(name in process_name for name in cursor_names)
def kill_cursor_processes(self, force: bool = False) -> bool:
"""Kill all Cursor processes"""
processes = self.find_cursor_processes()
success = True
for process_info in processes:
try:
proc = psutil.Process(process_info.pid)
if force:
proc.kill()
else:
proc.terminate()
logger.info(f"Terminated Cursor process: {process_info.name} (PID: {process_info.pid})")
except (psutil.NoSuchProcess, psutil.AccessDenied) as e:
logger.warning(f"Failed to terminate process {process_info.pid}: {e}")
success = False
return success
def wait_for_process_termination(self, pids: List[int], timeout: int = 30) -> bool:
"""Wait for processes to terminate"""
start_time = time.time()
while time.time() - start_time < timeout:
remaining_pids = []
for pid in pids:
try:
if psutil.pid_exists(pid):
remaining_pids.append(pid)
except Exception:
continue
if not remaining_pids:
return True
time.sleep(0.5)
logger.warning(f"Timeout waiting for process termination: {remaining_pids}")
return False
def monitor_process(self, pid: int, callback: Callable[[ProcessInfo], None]) -> None:
"""Monitor process and call callback with updates"""
def monitor():
try:
proc = psutil.Process(pid)
while proc.is_running():
try:
process_info = ProcessInfo(
pid=proc.pid,
name=proc.name(),
status=ProcessStatus.RUNNING,
memory_usage=proc.memory_info().rss / 1024 / 1024,
cpu_usage=proc.cpu_percent(),
start_time=proc.create_time(),
command_line=' '.join(proc.cmdline()) if proc.cmdline() else ''
)
callback(process_info)
time.sleep(1)
except (psutil.NoSuchProcess, psutil.AccessDenied):
break
except Exception as e:
logger.error(f"Process monitoring failed for PID {pid}: {e}")
thread = threading.Thread(target=monitor, daemon=True)
thread.start()
self._monitoring_threads[pid] = thread
class EnhancedSystemManager:
"""Enhanced system information and management"""
def __init__(self):
self._system_info = None
def get_system_info(self) -> SystemInfo:
"""Get comprehensive system information"""
if self._system_info is None:
try:
cpu_count = psutil.cpu_count()
if cpu_count is None:
cpu_count = 1
self._system_info = SystemInfo(
platform=platform.system(),
architecture=platform.machine(),
python_version=sys.version,
total_memory=psutil.virtual_memory().total / 1024 / 1024 / 1024,
available_memory=psutil.virtual_memory().available / 1024 / 1024 / 1024,
cpu_count=cpu_count,
disk_usage=self._get_disk_usage()
)
except Exception as e:
logger.error(f"Failed to get system info: {e}")
# Return basic info
self._system_info = SystemInfo(
platform=platform.system(),
architecture=platform.machine(),
python_version=sys.version,
total_memory=0.0,
available_memory=0.0,
cpu_count=1,
disk_usage={}
)
return self._system_info
def _get_disk_usage(self) -> Dict[str, float]:
disk_usage = {}
try:
for partition in psutil.disk_partitions():
try:
usage = psutil.disk_usage(partition.mountpoint)
disk_usage[partition.mountpoint] = {
'total': usage.total / 1024 / 1024 / 1024,
'used': usage.used / 1024 / 1024 / 1024,
'free': usage.free / 1024 / 1024 / 1024,
'percent': usage.percent
}
except (OSError, PermissionError):
continue
except Exception as e:
logger.warning(f"Failed to get disk usage: {e}")
return disk_usage
def check_system_requirements(self) -> Dict[str, bool]:
requirements = {
'python_version': sys.version_info >= (3, 8),
'memory_available': self.get_system_info().available_memory >= 2.0,
'disk_space': self._check_disk_space(),
'permissions': self._check_permissions()
}
return requirements
def _check_disk_space(self) -> bool:
try:
check_path = os.path.expanduser("~")
usage = psutil.disk_usage(check_path)
free_gb = usage.free / 1024 / 1024 / 1024
return free_gb >= 1.0
except Exception:
return True
def _check_permissions(self) -> bool:
try:
test_file = os.path.join(os.path.expanduser("~"), ".cursor_free_vip_test")
with open(test_file, 'w') as f:
f.write("test")
os.remove(test_file)
return True
except Exception:
return False
class EnhancedNetworkManager:
def __init__(self, timeout: int = 30, max_retries: int = 3):
self.timeout = timeout
self.max_retries = max_retries
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Cursor-Free-VIP/1.0'
})
def make_request(self, url: str, method: str = 'GET', **kwargs) -> Optional[requests.Response]:
for attempt in range(self.max_retries):
try:
response = self.session.request(
method=method,
url=url,
timeout=self.timeout,
**kwargs
)
response.raise_for_status()
return response
except requests.exceptions.RequestException as e:
logger.warning(f"Request attempt {attempt + 1} failed: {e}")
if attempt == self.max_retries - 1:
logger.error(f"All request attempts failed for {url}")
return None
time.sleep(2 ** attempt)
return None
def check_connectivity(self, urls: Optional[List[str]] = None) -> Dict[str, bool]:
if urls is None:
urls = [
'https://www.google.com',
'https://github.com',
'https://cursor.sh'
]
results = {}
with ThreadPoolExecutor(max_workers=5) as executor:
future_to_url = {
executor.submit(self._check_single_url, url): url
for url in urls
}
for future in as_completed(future_to_url):
url = future_to_url[future]
try:
results[url] = future.result()
except Exception as e:
logger.error(f"Failed to check {url}: {e}")
results[url] = False
return results
def _check_single_url(self, url: str) -> bool:
try:
response = self.make_request(url, timeout=10)
return response is not None and response.status_code == 200
except Exception:
return False
path_manager = EnhancedPathManager()
browser_manager = EnhancedBrowserManager()
process_manager = EnhancedProcessManager()
system_manager = EnhancedSystemManager()
network_manager = EnhancedNetworkManager()
def get_user_documents_path() -> str:
return path_manager.get_user_documents_path()
def find_executable(executable_names: List[str]) -> Optional[str]:
return path_manager.find_executable(executable_names)
def get_default_browser_path(browser_type: str = 'chrome') -> str:
return browser_manager.get_browser_path(browser_type)
def get_default_driver_path(browser_type: str = 'chrome') -> str:
return browser_manager.get_driver_path(browser_type)
def parse_time_range(time_str: str) -> Tuple[float, float]:
try:
if '-' in time_str:
parts = time_str.split('-')
if len(parts) == 2:
return float(parts[0].strip()), float(parts[1].strip())
else:
value = float(time_str.strip())
return value, value
except (ValueError, TypeError) as e:
logger.warning(f"Failed to parse time range '{time_str}': {e}")
return 0.5, 1.5
def get_random_wait_time(config: Dict, timing_key: str, default_range: Tuple[float, float] = (0.5, 1.5)) -> float:
try:
if timing_key in config:
time_range = parse_time_range(config[timing_key])
return random.uniform(time_range[0], time_range[1])
else:
return random.uniform(default_range[0], default_range[1])
except Exception as e:
logger.warning(f"Failed to get random wait time for {timing_key}: {e}")
return random.uniform(default_range[0], default_range[1])
def get_linux_cursor_path() -> str:
possible_paths = [
"/opt/Cursor/resources/app",
"/usr/share/cursor/resources/app",
"/usr/local/share/cursor/resources/app",
os.path.expanduser("~/.local/share/cursor/resources/app"),
os.path.expanduser("~/snap/cursor/current/usr/share/cursor/resources/app")
]
for path in possible_paths:
if os.path.exists(path):
return path
cursor_executable = path_manager.find_executable(['cursor'])
if cursor_executable:
exec_dir = os.path.dirname(cursor_executable)
possible_resource_paths = [
os.path.join(exec_dir, "resources", "app"),
os.path.join(os.path.dirname(exec_dir), "resources", "app"),
os.path.join(exec_dir, "..", "resources", "app")
]
for path in possible_resource_paths:
if os.path.exists(path):
return os.path.abspath(path)
return ""

View File

@@ -1,10 +1,23 @@
import requests
import json
import time
from colorama import Fore, Style
import logging
import os
from typing import Optional, Dict, Any, Union
from colorama import Fore, Style, init
from config import get_config
# Initialize colorama
init(autoreset=True)
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger(__name__)
# Define emoji constants
EMOJI = {
'START': '🚀',
@@ -13,35 +26,85 @@ EMOJI = {
'ERROR': '',
'WAIT': '',
'INFO': '',
'WARNING': '⚠️'
'WARNING': '⚠️',
'TOKEN': '🔖',
'REFRESH': '🔄'
}
def refresh_token(token, translator=None):
"""Refresh the token using the Chinese server API
def _get_message(translator: Any, key: str, fallback: str, **kwargs) -> str:
"""Get translated message or fallback.
Args:
token (str): The full WorkosCursorSessionToken cookie value
translator: Translator object
key: Translation key
fallback: Fallback message if translation not available
**kwargs: Format parameters for the message
Returns:
str: Translated or fallback message
"""
if translator:
return translator.get(key, **kwargs)
return fallback.format(**kwargs) if kwargs else fallback
def refresh_token(token: str, translator: Any = None) -> str:
"""Refresh the token using the refresh server API.
Args:
token: The full WorkosCursorSessionToken cookie value
translator: Optional translator object
Returns:
str: The refreshed access token or original token if refresh fails
"""
try:
logger.info("Attempting to refresh token")
# Validate input
if not token or not isinstance(token, str):
logger.error("Invalid token provided")
print(f"{Fore.RED}{EMOJI['ERROR']} {_get_message(translator, 'token.invalid_token', 'Invalid token provided')}{Style.RESET_ALL}")
return token
# Get configuration
config = get_config(translator)
# Check if token refresh is enabled
if config.has_option('Token', 'enable_refresh') and not config.getboolean('Token', 'enable_refresh'):
logger.info("Token refresh is disabled in configuration")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {_get_message(translator, 'token.refresh_disabled', 'Token refresh is disabled in configuration')}{Style.RESET_ALL}")
return _extract_token_part(token)
# Get refresh_server URL from config or use default
refresh_server = config.get('Token', 'refresh_server', fallback='https://token.cursorpro.com.cn')
logger.info(f"Using refresh server: {refresh_server}")
# Ensure the token is URL encoded properly
if '%3A%3A' not in token and '::' in token:
# Replace :: with URL encoded version if needed
token = token.replace('::', '%3A%3A')
encoded_token = _ensure_token_encoded(token)
# Make the request to the refresh server
url = f"{refresh_server}/reftoken?token={token}"
url = f"{refresh_server}/reftoken?token={encoded_token}"
print(f"{Fore.CYAN}{EMOJI['INFO']} {translator.get('token.refreshing') if translator else 'Refreshing token...'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['REFRESH']} {_get_message(translator, 'token.refreshing', 'Refreshing token...')}{Style.RESET_ALL}")
response = requests.get(url, timeout=30)
# Set timeout and headers
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept': 'application/json'
}
# Make request with retry logic
max_retries = 3
for attempt in range(max_retries):
try:
response = requests.get(url, headers=headers, timeout=30)
break
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e:
if attempt < max_retries - 1:
logger.warning(f"Request attempt {attempt + 1} failed: {e}. Retrying...")
time.sleep(2)
else:
raise
if response.status_code == 200:
try:
@@ -53,60 +116,140 @@ def refresh_token(token, translator=None):
expire_time = data.get('data', {}).get('expire_time', 'Unknown')
if access_token:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {translator.get('token.refresh_success', days=days_left, expire=expire_time) if translator else f'Token refreshed successfully! Valid for {days_left} days (expires: {expire_time})'}{Style.RESET_ALL}")
logger.info(f"Token refreshed successfully. Valid for {days_left} days")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {_get_message(translator, 'token.refresh_success', 'Token refreshed successfully! Valid for {days} days (expires: {expire})', days=days_left, expire=expire_time)}{Style.RESET_ALL}")
return access_token
else:
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {translator.get('token.no_access_token') if translator else 'No access token in response'}{Style.RESET_ALL}")
logger.warning("No access token in response")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {_get_message(translator, 'token.no_access_token', 'No access token in response')}{Style.RESET_ALL}")
else:
error_msg = data.get('msg', 'Unknown error')
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.refresh_failed', error=error_msg) if translator else f'Token refresh failed: {error_msg}'}{Style.RESET_ALL}")
logger.error(f"Token refresh failed: {error_msg}")
print(f"{Fore.RED}{EMOJI['ERROR']} {_get_message(translator, 'token.refresh_failed', 'Token refresh failed: {error}', error=error_msg)}{Style.RESET_ALL}")
except json.JSONDecodeError:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.invalid_response') if translator else 'Invalid JSON response from refresh server'}{Style.RESET_ALL}")
logger.error("Invalid JSON response from refresh server")
print(f"{Fore.RED}{EMOJI['ERROR']} {_get_message(translator, 'token.invalid_response', 'Invalid JSON response from refresh server')}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.server_error', status=response.status_code) if translator else f'Refresh server error: HTTP {response.status_code}'}{Style.RESET_ALL}")
logger.error(f"Refresh server error: HTTP {response.status_code}")
print(f"{Fore.RED}{EMOJI['ERROR']} {_get_message(translator, 'token.server_error', 'Refresh server error: HTTP {status}', status=response.status_code)}{Style.RESET_ALL}")
except requests.exceptions.Timeout:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.request_timeout') if translator else 'Request to refresh server timed out'}{Style.RESET_ALL}")
logger.error("Request to refresh server timed out")
print(f"{Fore.RED}{EMOJI['ERROR']} {_get_message(translator, 'token.request_timeout', 'Request to refresh server timed out')}{Style.RESET_ALL}")
except requests.exceptions.ConnectionError:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.connection_error') if translator else 'Connection error to refresh server'}{Style.RESET_ALL}")
logger.error("Connection error to refresh server")
print(f"{Fore.RED}{EMOJI['ERROR']} {_get_message(translator, 'token.connection_error', 'Connection error to refresh server')}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.unexpected_error', error=str(e)) if translator else f'Unexpected error during token refresh: {str(e)}'}{Style.RESET_ALL}")
logger.error(f"Unexpected error during token refresh: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {_get_message(translator, 'token.unexpected_error', 'Unexpected error during token refresh: {error}', error=str(e))}{Style.RESET_ALL}")
# Return original token if refresh fails
return token.split('%3A%3A')[-1] if '%3A%3A' in token else token.split('::')[-1] if '::' in token else token
# Return extracted token part if refresh fails
return _extract_token_part(token)
def get_token_from_cookie(cookie_value, translator=None):
"""Extract and process token from cookie value
def _ensure_token_encoded(token: str) -> str:
"""Ensure the token is properly URL encoded.
Args:
cookie_value (str): The WorkosCursorSessionToken cookie value
token: The token to encode
Returns:
str: The properly encoded token
"""
if '%3A%3A' not in token and '::' in token:
# Replace :: with URL encoded version if needed
return token.replace('::', '%3A%3A')
return token
def _extract_token_part(token: str) -> str:
"""Extract the token part from the cookie value.
Args:
token: The full cookie value
Returns:
str: The extracted token part
"""
if '%3A%3A' in token:
return token.split('%3A%3A')[-1]
elif '::' in token:
return token.split('::')[-1]
else:
return token
def get_token_from_cookie(cookie_value: str, translator: Any = None) -> str:
"""Extract and process token from cookie value.
Args:
cookie_value: The WorkosCursorSessionToken cookie value
translator: Optional translator object
Returns:
str: The processed token
"""
try:
logger.info("Processing token from cookie")
# Validate input
if not cookie_value or not isinstance(cookie_value, str):
logger.error("Invalid cookie value provided")
print(f"{Fore.RED}{EMOJI['ERROR']} {_get_message(translator, 'token.invalid_cookie', 'Invalid cookie value provided')}{Style.RESET_ALL}")
return ""
# Try to refresh the token with the API first
print(f"{Fore.CYAN}{EMOJI['TOKEN']} {_get_message(translator, 'token.processing', 'Processing token...')}{Style.RESET_ALL}")
refreshed_token = refresh_token(cookie_value, translator)
# If refresh succeeded and returned a different token, use it
if refreshed_token and refreshed_token != cookie_value:
original_token_part = _extract_token_part(cookie_value)
if refreshed_token and refreshed_token != original_token_part and refreshed_token != cookie_value:
logger.info("Using refreshed token")
return refreshed_token
# If refresh failed or returned same token, use traditional extraction method
if '%3A%3A' in cookie_value:
return cookie_value.split('%3A%3A')[-1]
elif '::' in cookie_value:
return cookie_value.split('::')[-1]
else:
return cookie_value
logger.info("Using extracted token part")
return original_token_part
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('token.extraction_error', error=str(e)) if translator else f'Error extracting token: {str(e)}'}{Style.RESET_ALL}")
logger.error(f"Error extracting token: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {_get_message(translator, 'token.extraction_error', 'Error extracting token: {error}', error=str(e))}{Style.RESET_ALL}")
# Fall back to original behavior
if '%3A%3A' in cookie_value:
return cookie_value.split('%3A%3A')[-1]
elif '::' in cookie_value:
return cookie_value.split('::')[-1]
return _extract_token_part(cookie_value)
def validate_token(token: str, translator: Any = None) -> bool:
"""Validate if the token looks legitimate.
Args:
token: The token to validate
translator: Optional translator object
Returns:
bool: True if token looks valid, False otherwise
"""
if not token:
logger.warning("Empty token provided")
print(f"{Fore.RED}{EMOJI['ERROR']} {_get_message(translator, 'token.empty_token', 'Empty token provided')}{Style.RESET_ALL}")
return False
# Basic validation - JWT tokens typically start with "eyJ"
if token.startswith('eyJ') and len(token) > 100 and '.' in token:
logger.info("Token appears to be in valid JWT format")
return True
logger.warning("Token does not appear to be in valid JWT format")
print(f"{Fore.YELLOW}{EMOJI['WARNING']} {_get_message(translator, 'token.invalid_format', 'Token does not appear to be in valid JWT format')}{Style.RESET_ALL}")
return False
if __name__ == "__main__":
# Test functionality if run directly
try:
test_token = input(f"{Fore.CYAN}{EMOJI['TOKEN']} Enter a token to test: {Style.RESET_ALL}")
processed_token = get_token_from_cookie(test_token)
print(f"\n{Fore.GREEN}{EMOJI['INFO']} Processed token: {processed_token}{Style.RESET_ALL}")
if validate_token(processed_token):
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} Token appears to be valid{Style.RESET_ALL}")
else:
return cookie_value
print(f"{Fore.YELLOW}{EMOJI['WARNING']} Token may not be valid{Style.RESET_ALL}")
except Exception as e:
logger.error(f"Error in test: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} Test error: {str(e)}{Style.RESET_ALL}")

174
logo.py
View File

@@ -1,101 +1,103 @@
import sys
import platform
import logging
from colorama import Fore, Style, init
from dotenv import load_dotenv
import os
import shutil
import re
# Get the current script directory
current_dir = os.path.dirname(os.path.abspath(__file__))
# Build the full path to the .env file
env_path = os.path.join(current_dir, '.env')
# Load environment variables, specifying the .env file path
load_dotenv(env_path)
# Get the version number, using the default value if not found
version = os.getenv('VERSION', '1.0.0')
from typing import Optional, Dict, List, Any, Tuple, Union
# Initialize colorama
init()
init(autoreset=True)
# get terminal width
def get_terminal_width():
try:
columns, _ = shutil.get_terminal_size()/2
return columns
except:
return 80 # default width
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger(__name__)
# center display text (not handling Chinese characters)
def center_multiline_text(text, handle_chinese=False):
width = get_terminal_width()
lines = text.split('\n')
centered_lines = []
for line in lines:
# calculate actual display width (remove ANSI color codes)
clean_line = line
for color in [Fore.CYAN, Fore.YELLOW, Fore.GREEN, Fore.RED, Fore.BLUE, Style.RESET_ALL]:
clean_line = clean_line.replace(color, '')
# remove all ANSI escape sequences to get the actual length
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
clean_line = ansi_escape.sub('', clean_line)
# calculate display width
if handle_chinese:
# consider Chinese characters occupying two positions
display_width = 0
for char in clean_line:
if ord(char) > 127: # non-ASCII characters
display_width += 2
else:
display_width += 1
else:
# not handling Chinese characters
display_width = len(clean_line)
# calculate the number of spaces to add
padding = max(0, (width - display_width) // 2)
centered_lines.append(' ' * padding + line)
return '\n'.join(centered_lines)
# Current version
version = "1.9.9"
# original LOGO text
LOGO_TEXT = f"""{Fore.CYAN}
██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ██████╗ ██████╗
██╔════╝██║ ████╔══██╗██╔════╝██╔═══██╗██╔══██╗ ██╔══██╗██╔══██╗██╔═══██╗
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ ██████╔╝██████╔╝██║ ██║
██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ ██╔═══╝ ██╔══██╗██║ ██║
██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ ██║ ██║ ██║╚██████╔╝
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝
# ASCII art logo
LOGO = f"""
{Fore.CYAN}
██████╗██╗ ████████╗ ███████╗ ██████╗ ██████╗ ███████╗██████╗ ███████╗███████╗ ██╗ ██╗██╗██████╗
██╔════╝██║ ██║██╔══████╔════╝██╔═══████╔══██╗ ██╔════╝██╔══████╔════╝██╔════╝ ██║ ██║██║██╔══██╗
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ █████╗ ██████╔╝█████╗ █████╗ ██║ ██║██║██████╔╝
██║ ██║ ████╔══██╗╚════██║██║ ██║██╔══██╗ ██╔══╝ ██╔══████╔══╝ ██╔══╝ ╚██╗ ██╔╝██║██╔═══╝
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ ██║ ██║ ██║███████╗███████╗ ╚████╔╝ ██║██║
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝ ╚═══╝ ╚═╝╚═╝
{Style.RESET_ALL}"""
DESCRIPTION_TEXT = f"""{Fore.YELLOW}
Pro Version Activator v{version}{Fore.GREEN}
Author: Pin Studios (yeongpin)"""
# Simplified logo for terminals with limited width
SIMPLIFIED_LOGO = f"""
{Fore.CYAN}
██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗
██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝
██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝
{Fore.GREEN}FREE VIP {version}{Style.RESET_ALL}
{Style.RESET_ALL}"""
CONTRIBUTORS_TEXT = f"""{Fore.BLUE}
Contributors:
BasaiCorp aliensb handwerk2016 Nigel1992
UntaDotMy RenjiYuusei imbajin ahmed98Osama
bingoohuang mALIk-sHAHId MFaiqKhan httpmerak
muhammedfurkan plamkatawe Lucaszmv
# Contributors info
CURSOR_CONTRIBUTORS = f"""
{Fore.CYAN}╔══════════════════════════════════════════════════════════════════╗
{Fore.YELLOW}CURSOR FREE VIP{Fore.CYAN}
╠══════════════════════════════════════════════════════════════════╣
{Fore.GREEN}Author:{Fore.WHITE} yeongpin {Fore.CYAN}
{Fore.GREEN}GitHub:{Fore.WHITE} https://github.com/yeongpin/cursor-free-vip {Fore.CYAN}
{Fore.GREEN}Version:{Fore.WHITE} {version} {Fore.CYAN}
╚══════════════════════════════════════════════════════════════════╝{Style.RESET_ALL}
"""
OTHER_INFO_TEXT = f"""{Fore.YELLOW}
Github: https://github.com/yeongpin/cursor-free-vip{Fore.RED}
Press 4 to change language | 按下 4 键切换语言{Style.RESET_ALL}"""
# center display LOGO and DESCRIPTION
CURSOR_LOGO = center_multiline_text(LOGO_TEXT, handle_chinese=False)
CURSOR_DESCRIPTION = center_multiline_text(DESCRIPTION_TEXT, handle_chinese=False)
CURSOR_CONTRIBUTORS = center_multiline_text(CONTRIBUTORS_TEXT, handle_chinese=False)
CURSOR_OTHER_INFO = center_multiline_text(OTHER_INFO_TEXT, handle_chinese=True)
def get_terminal_width() -> int:
"""Get terminal width with fallback for different platforms.
Returns:
int: Terminal width in characters
"""
try:
# Try to get terminal size using different methods based on platform
if platform.system() == "Windows":
from shutil import get_terminal_size
columns = get_terminal_size().columns
else:
import os
columns = os.get_terminal_size().columns
return columns
except Exception as e:
logger.warning(f"Failed to get terminal width: {e}")
# Default width if detection fails
return 80
def print_logo():
print(CURSOR_LOGO)
print(CURSOR_DESCRIPTION)
# print(CURSOR_CONTRIBUTORS)
print(CURSOR_OTHER_INFO)
def print_logo() -> None:
"""Print logo with version information based on terminal width."""
try:
# Get terminal width
terminal_width = get_terminal_width()
# Choose logo based on terminal width
if terminal_width < 100:
logo = SIMPLIFIED_LOGO
else:
logo = LOGO
# Print logo
print(logo)
# Print version info
print(f"{Fore.GREEN}Version: {version}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'' * min(80, terminal_width)}{Style.RESET_ALL}")
except Exception as e:
logger.error(f"Error printing logo: {e}")
# Fallback to simplified version if any error occurs
print(SIMPLIFIED_LOGO)
print(f"{Fore.GREEN}Version: {version}{Style.RESET_ALL}")
if __name__ == "__main__":
print_logo()
print(CURSOR_CONTRIBUTORS)

351
main.py
View File

@@ -2,34 +2,49 @@
# This script allows the user to choose which script to run.
import os
import sys
import json
from logo import print_logo, version
from colorama import Fore, Style, init
import locale
import platform
import requests
import subprocess
import logging
from colorama import Fore, Style, init
from typing import Optional, Dict, List, Any, Tuple, Union
from pathlib import Path
# Initialize colorama
init(autoreset=True)
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger(__name__)
# Import local modules
from logo import print_logo, version
from config import get_config, force_update_config
import shutil
import re
from utils import get_user_documents_path
from utils import get_user_documents_path
# Add these imports for Arabic support
try:
import arabic_reshaper
from bidi.algorithm import get_display
except ImportError:
logger.warning("Arabic support modules not found")
arabic_reshaper = None
get_display = None
# Only import windll on Windows systems
if platform.system() == 'Windows':
import ctypes
# Only import windll on Windows systems
from ctypes import windll
# Initialize colorama
init()
try:
import ctypes
from ctypes import windll
except ImportError:
logger.warning("Windows-specific modules not found")
ctypes = None
windll = None
# Define emoji and color constants
EMOJI = {
@@ -53,36 +68,38 @@ EMOJI = {
}
# Function to check if running as frozen executable
def is_frozen():
def is_frozen() -> bool:
"""Check if the script is running as a frozen executable."""
return getattr(sys, 'frozen', False)
# Function to check admin privileges (Windows only)
def is_admin():
def is_admin() -> bool:
"""Check if the script is running with admin privileges (Windows only)."""
if platform.system() == 'Windows':
if platform.system() == 'Windows' and ctypes and windll:
try:
return ctypes.windll.shell32.IsUserAnAdmin() != 0
except Exception:
except Exception as e:
logger.error(f"Error checking admin privileges: {e}")
return False
# Always return True for non-Windows to avoid changing behavior
return True
# Function to restart with admin privileges
def run_as_admin():
def run_as_admin() -> bool:
"""Restart the current script with admin privileges (Windows only)."""
if platform.system() != 'Windows':
if platform.system() != 'Windows' or not ctypes or not windll:
return False
try:
args = [sys.executable] + sys.argv
# Request elevation via ShellExecute
print(f"{Fore.YELLOW}{EMOJI['ADMIN']} Requesting administrator privileges...{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['ADMIN']} {translator.get('admin.requesting_privileges') if translator else 'Requesting administrator privileges...'}{Style.RESET_ALL}")
ctypes.windll.shell32.ShellExecuteW(None, "runas", args[0], " ".join('"' + arg + '"' for arg in args[1:]), None, 1)
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} Failed to restart with admin privileges: {e}{Style.RESET_ALL}")
logger.error(f"Failed to restart with admin privileges: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('admin.restart_failed', error=str(e)) if translator else f'Failed to restart with admin privileges: {e}'}{Style.RESET_ALL}")
return False
class Translator:
@@ -687,119 +704,187 @@ def check_latest_version():
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.continue_anyway')}{Style.RESET_ALL}")
return
def main():
# Check for admin privileges if running as executable on Windows only
if platform.system() == 'Windows' and is_frozen() and not is_admin():
print(f"{Fore.YELLOW}{EMOJI['ADMIN']} {translator.get('menu.admin_required')}{Style.RESET_ALL}")
if run_as_admin():
sys.exit(0) # Exit after requesting admin privileges
else:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.admin_required_continue')}{Style.RESET_ALL}")
print_logo()
# Initialize configuration
config = get_config(translator)
if not config:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.config_init_failed')}{Style.RESET_ALL}")
return
force_update_config(translator)
if config.getboolean('Utils', 'enabled_update_check'):
check_latest_version() # Add version check before showing menu
print_menu()
while True:
try:
choice_num = 17
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices=f'0-{choice_num}')}: {Style.RESET_ALL}")
match choice:
case "0":
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.exit')}...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'' * 50}{Style.RESET_ALL}")
return
case "1":
import reset_machine_manual
reset_machine_manual.run(translator)
print_menu()
case "2":
import cursor_register_manual
cursor_register_manual.main(translator)
print_menu()
case "3":
import quit_cursor
quit_cursor.quit_cursor(translator)
print_menu()
case "4":
if select_language():
print_menu()
continue
case "5":
from oauth_auth import main as oauth_main
oauth_main('google',translator)
print_menu()
case "6":
from oauth_auth import main as oauth_main
oauth_main('github',translator)
print_menu()
case "7":
import disable_auto_update
disable_auto_update.run(translator)
print_menu()
case "8":
import totally_reset_cursor
totally_reset_cursor.run(translator)
# print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.fixed_soon')}{Style.RESET_ALL}")
print_menu()
case "9":
import logo
print(logo.CURSOR_CONTRIBUTORS)
print_menu()
case "10":
from config import print_config
print_config(get_config(), translator)
print_menu()
case "11":
import bypass_version
bypass_version.main(translator)
print_menu()
case "12":
import check_user_authorized
check_user_authorized.main(translator)
print_menu()
case "13":
import bypass_token_limit
bypass_token_limit.run(translator)
print_menu()
case "14":
import restore_machine_id
restore_machine_id.run(translator)
print_menu()
case "15":
import delete_cursor_google
delete_cursor_google.main(translator)
print_menu()
case "16":
from oauth_auth import OAuthHandler
oauth = OAuthHandler(translator)
oauth._select_profile()
print_menu()
case "17":
import manual_custom_auth
manual_custom_auth.main(translator)
print_menu()
case _:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}")
print_menu()
except KeyboardInterrupt:
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.program_terminated')}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'' * 50}{Style.RESET_ALL}")
def main() -> None:
"""Main entry point for the application."""
try:
# Check for admin privileges if running as executable on Windows only
if platform.system() == 'Windows' and is_frozen() and not is_admin():
logger.warning("Running without admin privileges on Windows")
print(f"{Fore.YELLOW}{EMOJI['ADMIN']} {translator.get('menu.admin_required') if translator else 'Administrator privileges are required for some features'}{Style.RESET_ALL}")
if run_as_admin():
sys.exit(0) # Exit after requesting admin privileges
else:
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.admin_required_continue') if translator else 'Continuing without administrator privileges. Some features may not work correctly.'}{Style.RESET_ALL}")
# Display logo
print_logo()
# Initialize configuration
logger.info("Initializing configuration")
config = get_config(translator)
if not config:
logger.error("Failed to initialize configuration")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.config_init_failed') if translator else 'Failed to initialize configuration'}{Style.RESET_ALL}")
return
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.error_occurred', error=str(e))}{Style.RESET_ALL}")
print_menu()
# Update configuration
force_update_config(translator)
# Check for updates
if config.has_option('Utils', 'enabled_update_check') and config.getboolean('Utils', 'enabled_update_check'):
logger.info("Checking for updates")
check_latest_version()
# Display menu
print_menu()
# Main menu loop
while True:
try:
choice_num = 17
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('menu.input_choice', choices=f'0-{choice_num}') if translator else f'Enter your choice (0-{choice_num})'}: {Style.RESET_ALL}")
# Process menu choice
process_menu_choice(choice)
except KeyboardInterrupt:
logger.info("Program terminated by user (KeyboardInterrupt)")
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.program_terminated') if translator else 'Program terminated by user'}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'' * 50}{Style.RESET_ALL}")
return
except Exception as e:
logger.error(f"Unexpected error in main loop: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.unexpected_error', error=str(e)) if translator else f'An unexpected error occurred: {e}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.please_try_again') if translator else 'Please try again'}{Style.RESET_ALL}")
except Exception as e:
logger.critical(f"Critical error in main function: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.critical_error', error=str(e)) if translator else f'A critical error occurred: {e}'}{Style.RESET_ALL}")
print(f"{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.please_restart') if translator else 'Please restart the application'}{Style.RESET_ALL}")
return
def process_menu_choice(choice: str) -> None:
"""Process menu choice and execute corresponding action.
Args:
choice: User's menu choice
"""
try:
match choice:
case "0":
logger.info("User selected to exit")
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.exit') if translator else 'Exiting'}...{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'' * 50}{Style.RESET_ALL}")
sys.exit(0)
case "1":
logger.info("User selected reset machine manual")
import reset_machine_manual
reset_machine_manual.run(translator)
print_menu()
case "2":
logger.info("User selected cursor register manual")
import cursor_register_manual
cursor_register_manual.main(translator)
print_menu()
case "3":
logger.info("User selected quit cursor")
import quit_cursor
quit_cursor.quit_cursor(translator)
print_menu()
case "4":
logger.info("User selected language settings")
if select_language():
print_menu()
return
case "5":
logger.info("User selected Google OAuth")
from oauth_auth import main as oauth_main
oauth_main('google', translator)
print_menu()
case "6":
logger.info("User selected GitHub OAuth")
from oauth_auth import main as oauth_main
oauth_main('github', translator)
print_menu()
case "7":
logger.info("User selected disable auto update")
import disable_auto_update
disable_auto_update.run(translator)
print_menu()
case "8":
logger.info("User selected totally reset cursor")
import totally_reset_cursor
totally_reset_cursor.run(translator)
print_menu()
case "9":
logger.info("User selected view contributors")
import logo
print(logo.CURSOR_CONTRIBUTORS)
print_menu()
case "10":
logger.info("User selected view config")
from config import print_config
print_config(get_config(), translator)
print_menu()
case "11":
logger.info("User selected bypass version")
import bypass_version
bypass_version.main(translator)
print_menu()
case "12":
logger.info("User selected check user authorized")
import check_user_authorized
check_user_authorized.main(translator)
print_menu()
case "13":
logger.info("User selected bypass token limit")
import bypass_token_limit
bypass_token_limit.run(translator)
print_menu()
case "14":
logger.info("User selected restore machine ID")
import restore_machine_id
restore_machine_id.run(translator)
print_menu()
case "15":
logger.info("User selected delete cursor Google")
import delete_cursor_google
delete_cursor_google.main(translator)
print_menu()
case "16":
logger.info("User selected select profile")
from oauth_auth import OAuthHandler
oauth = OAuthHandler(translator)
oauth._select_profile()
print_menu()
case "17":
logger.info("User selected manual custom auth")
import manual_custom_auth
manual_custom_auth.main(translator)
print_menu()
case _:
logger.warning(f"Invalid choice: {choice}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice') if translator else 'Invalid choice'}{Style.RESET_ALL}")
print_menu()
except ImportError as e:
logger.error(f"Failed to import module: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.module_not_found', error=str(e)) if translator else f'Module not found: {e}'}{Style.RESET_ALL}")
print_menu()
except Exception as e:
logger.error(f"Error processing menu choice: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.action_failed', error=str(e)) if translator else f'Action failed: {e}'}{Style.RESET_ALL}")
print_menu()
if __name__ == "__main__":
main()
# Initialize translator
translator = Translator()
try:
main()
except KeyboardInterrupt:
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('menu.program_terminated') if translator else 'Program terminated by user'}{Style.RESET_ALL}")
sys.exit(0)
except Exception as e:
logger.critical(f"Unhandled exception: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.unhandled_exception', error=str(e)) if translator else f'Unhandled exception: {e}'}{Style.RESET_ALL}")
sys.exit(1)

View File

@@ -1,11 +1,22 @@
import psutil
import time
from colorama import Fore, Style, init
import logging
import platform
import sys
import os
from colorama import Fore, Style, init
from typing import Optional, Dict, List, Any, Tuple, Union, Set
# Initialize colorama
init()
init(autoreset=True)
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger(__name__)
# Define emoji constants
EMOJI = {
@@ -13,43 +24,126 @@ EMOJI = {
"SUCCESS": "",
"ERROR": "",
"INFO": "",
"WAIT": ""
"WAIT": "",
"KILL": "🛑",
"SEARCH": "🔍"
}
class CursorQuitter:
def __init__(self, timeout=5, translator=None):
self.timeout = timeout
self.translator = translator # Use the passed translator
"""Class to handle termination of Cursor processes."""
def __init__(self, timeout: int = 5, translator: Any = None):
"""Initialize CursorQuitter.
def quit_cursor(self):
"""Gently close Cursor processes"""
try:
print(f"{Fore.CYAN}{EMOJI['PROCESS']} {self.translator.get('quit_cursor.start')}...{Style.RESET_ALL}")
cursor_processes = []
Args:
timeout: Maximum time to wait for processes to terminate naturally
translator: Optional translator for internationalization
"""
self.timeout = max(1, timeout) # Ensure timeout is at least 1 second
self.translator = translator
def _get_message(self, key: str, fallback: str, **kwargs) -> str:
"""Get translated message or fallback.
Args:
key: Translation key
fallback: Fallback message if translation not available
**kwargs: Format parameters for the message
# Collect all Cursor processes
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):
Returns:
str: Translated or fallback message
"""
if self.translator:
return self.translator.get(key, **kwargs)
return fallback.format(**kwargs) if kwargs else fallback
def _find_cursor_processes(self) -> List[psutil.Process]:
"""Find all Cursor processes.
Returns:
List[psutil.Process]: List of Cursor processes
"""
cursor_processes = []
cursor_names = {
'windows': ['cursor.exe', 'cursor helper.exe', 'cursor crash handler.exe'],
'darwin': ['Cursor', 'Cursor Helper', 'Cursor Crash Handler'],
'linux': ['cursor', 'cursor-helper', 'cursor-crash-handler']
}
# Get platform-specific process names
system = platform.system().lower()
if system in cursor_names:
target_names = cursor_names[system]
else:
# Fallback to all possible names
target_names = [name for names in cursor_names.values() for name in names]
logger.info(f"Looking for Cursor processes with names: {target_names}")
print(f"{Fore.CYAN}{EMOJI['SEARCH']} {self._get_message('quit_cursor.searching', 'Searching for Cursor processes...')}{Style.RESET_ALL}")
# Collect all Cursor processes
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
try:
proc_name = proc.info['name'].lower() if proc.info['name'] else ""
# Check process name
if any(target.lower() in proc_name for target in target_names):
cursor_processes.append(proc)
continue
# Check command line for additional detection
if proc.info['cmdline']:
cmdline = " ".join(proc.info['cmdline']).lower()
if 'cursor' in cmdline and ('electron' in cmdline or 'app' in cmdline):
cursor_processes.append(proc)
except (psutil.NoSuchProcess, psutil.AccessDenied, Exception) as e:
logger.warning(f"Error accessing process: {e}")
continue
return cursor_processes
def quit_cursor(self) -> bool:
"""Gently close Cursor processes.
Returns:
bool: True if all processes were terminated successfully, False otherwise
"""
try:
msg = self._get_message('quit_cursor.start', 'Attempting to close Cursor processes')
logger.info(msg)
print(f"{Fore.CYAN}{EMOJI['PROCESS']} {msg}...{Style.RESET_ALL}")
# Find Cursor processes
cursor_processes = self._find_cursor_processes()
if not cursor_processes:
print(f"{Fore.GREEN}{EMOJI['INFO']} {self.translator.get('quit_cursor.no_process')}{Style.RESET_ALL}")
msg = self._get_message('quit_cursor.no_process', 'No Cursor processes found')
logger.info(msg)
print(f"{Fore.GREEN}{EMOJI['INFO']} {msg}{Style.RESET_ALL}")
return True
# Log found processes
logger.info(f"Found {len(cursor_processes)} Cursor processes")
print(f"{Fore.CYAN}{EMOJI['INFO']} {self._get_message('quit_cursor.processes_found', 'Found {count} Cursor processes', count=len(cursor_processes))}{Style.RESET_ALL}")
# Gently request processes to terminate
for proc in cursor_processes:
try:
if proc.is_running():
print(f"{Fore.YELLOW}{EMOJI['PROCESS']} {self.translator.get('quit_cursor.terminating', pid=proc.pid)}...{Style.RESET_ALL}")
msg = self._get_message('quit_cursor.terminating', 'Terminating process {pid}', pid=proc.pid)
logger.info(f"Terminating process {proc.pid}")
print(f"{Fore.YELLOW}{EMOJI['PROCESS']} {msg}...{Style.RESET_ALL}")
proc.terminate()
except (psutil.NoSuchProcess, psutil.AccessDenied):
except (psutil.NoSuchProcess, psutil.AccessDenied, Exception) as e:
logger.warning(f"Error terminating process {proc.pid}: {e}")
continue
# Wait for processes to terminate naturally
print(f"{Fore.CYAN}{EMOJI['WAIT']} {self.translator.get('quit_cursor.waiting')}...{Style.RESET_ALL}")
msg = self._get_message('quit_cursor.waiting', f'Waiting up to {self.timeout} seconds for processes to close')
logger.info(f"Waiting up to {self.timeout} seconds for processes to close")
print(f"{Fore.CYAN}{EMOJI['WAIT']} {msg}...{Style.RESET_ALL}")
start_time = time.time()
while time.time() - start_time < self.timeout:
still_running = []
@@ -57,33 +151,89 @@ class CursorQuitter:
try:
if proc.is_running():
still_running.append(proc)
except (psutil.NoSuchProcess, psutil.AccessDenied):
except (psutil.NoSuchProcess, psutil.AccessDenied, Exception):
continue
if not still_running:
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('quit_cursor.success')}{Style.RESET_ALL}")
msg = self._get_message('quit_cursor.success', 'All Cursor processes have been closed successfully')
logger.info("All Cursor processes have been closed successfully")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {msg}{Style.RESET_ALL}")
return True
time.sleep(0.5)
# If processes are still running after timeout
# If processes are still running after timeout, try to kill them
if still_running:
process_list = ", ".join([str(p.pid) for p in still_running])
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('quit_cursor.timeout', pids=process_list)}{Style.RESET_ALL}")
return False
msg = self._get_message('quit_cursor.timeout', 'Timeout reached. Some processes are still running: {pids}', pids=process_list)
logger.warning(f"Timeout reached. Still running: {process_list}")
print(f"{Fore.YELLOW}{EMOJI['WAIT']} {msg}{Style.RESET_ALL}")
# Try to kill remaining processes
print(f"{Fore.RED}{EMOJI['KILL']} {self._get_message('quit_cursor.force_kill', 'Attempting to force kill remaining processes')}{Style.RESET_ALL}")
for proc in still_running:
try:
if proc.is_running():
logger.info(f"Force killing process {proc.pid}")
proc.kill()
except (psutil.NoSuchProcess, psutil.AccessDenied, Exception) as e:
logger.error(f"Error killing process {proc.pid}: {e}")
continue
# Check if all processes are now killed
time.sleep(1)
final_check = [p for p in still_running if p.is_running()]
if not final_check:
msg = self._get_message('quit_cursor.force_success', 'All Cursor processes have been forcefully terminated')
logger.info("All Cursor processes have been forcefully terminated")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {msg}{Style.RESET_ALL}")
return True
else:
failed_list = ", ".join([str(p.pid) for p in final_check])
msg = self._get_message('quit_cursor.force_failed', 'Failed to terminate some processes: {pids}', pids=failed_list)
logger.error(f"Failed to terminate processes: {failed_list}")
print(f"{Fore.RED}{EMOJI['ERROR']} {msg}{Style.RESET_ALL}")
return False
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('quit_cursor.error', error=str(e))}{Style.RESET_ALL}")
logger.error(f"Error in quit_cursor: {e}")
msg = self._get_message('quit_cursor.error', 'An error occurred: {error}', error=str(e))
print(f"{Fore.RED}{EMOJI['ERROR']} {msg}{Style.RESET_ALL}")
return False
def quit_cursor(translator=None, timeout=5):
"""Convenient function for directly calling the quit function"""
quitter = CursorQuitter(timeout, translator)
return quitter.quit_cursor()
def quit_cursor(translator: Any = None, timeout: int = 5) -> bool:
"""Convenient function for directly calling the quit function.
Args:
translator: Optional translator for internationalization
timeout: Maximum time to wait for processes to terminate naturally
Returns:
bool: True if all processes were terminated successfully, False otherwise
"""
try:
quitter = CursorQuitter(timeout, translator)
return quitter.quit_cursor()
except Exception as e:
logger.error(f"Error in quit_cursor function: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} An unexpected error occurred: {str(e)}{Style.RESET_ALL}")
return False
if __name__ == "__main__":
# If run directly, use the default translator
from main import translator as main_translator
quit_cursor(main_translator)
try:
# If run directly, try to use the default translator
try:
from main import translator as main_translator
result = quit_cursor(main_translator)
except ImportError:
logger.warning("Failed to import translator from main.py, running without translation")
result = quit_cursor()
# Exit with appropriate status code
sys.exit(0 if result else 1)
except Exception as e:
logger.critical(f"Critical error: {e}")
print(f"{Fore.RED}{EMOJI['ERROR']} Critical error: {str(e)}{Style.RESET_ALL}")
sys.exit(1)

View File

@@ -1,13 +1,29 @@
watchdog
python-dotenv>=1.0.0
colorama>=0.4.6
requests
psutil>=5.8.0
pywin32; platform_system == "Windows"
pyinstaller
DrissionPage>=4.0.0
selenium
webdriver_manager
arabic-reshaper
python-bidi
faker
# Core dependencies
colorama>=0.4.6,<0.5.0
requests>=2.31.0,<3.0.0
python-dotenv>=1.0.0,<2.0.0
psutil>=5.9.5,<6.0.0
watchdog>=3.0.0,<4.0.0
# Web automation
selenium>=4.14.0,<5.0.0
webdriver_manager>=4.0.0,<5.0.0
DrissionPage>=4.0.0,<5.0.0
# Internationalization
arabic-reshaper>=3.0.0,<4.0.0
python-bidi>=0.4.2,<0.5.0
# Data generation
faker>=19.3.0,<20.0.0
# Packaging
pyinstaller>=6.0.0,<7.0.0
# Windows-specific dependencies
pywin32>=306; platform_system == "Windows"
# Optional dependencies
tqdm>=4.66.1,<5.0.0 # Progress bars
cryptography>=41.0.4,<42.0.0 # Secure encryption
pillow>=10.0.0,<11.0.0 # Image processing

449
utils.py
View File

@@ -2,233 +2,332 @@ import os
import sys
import platform
import random
import shutil
import logging
from typing import Optional, Dict, List, Union, Tuple
def get_user_documents_path():
"""Get user documents path"""
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
logger = logging.getLogger(__name__)
def get_user_documents_path() -> str:
"""Get user documents path across different operating systems.
Returns:
str: Path to user's Documents directory
"""
if platform.system() == "Windows":
try:
import winreg
# 打开注册表
with winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders") as key:
# 获取 "Personal" 键的值,这指向用户的文档目录
documents_path, _ = winreg.QueryValueEx(key, "Personal")
return documents_path
except Exception as e:
# fallback
logger.warning(f"Failed to get Documents path from registry: {e}")
return os.path.expanduser("~\\Documents")
else:
elif platform.system() == "Darwin": # macOS
return os.path.expanduser("~/Documents")
else: # Linux and other Unix-like systems
# Check for XDG user directories
try:
with open(os.path.expanduser("~/.config/user-dirs.dirs"), "r") as f:
for line in f:
if line.startswith("XDG_DOCUMENTS_DIR"):
path = line.split("=")[1].strip().strip('"').replace("$HOME", os.path.expanduser("~"))
if os.path.exists(path):
return path
except (FileNotFoundError, IOError):
pass
# Fallback to ~/Documents
return os.path.expanduser("~/Documents")
def get_default_driver_path(browser_type='chrome'):
"""Get default driver path based on browser type"""
browser_type = browser_type.lower()
if browser_type == 'chrome':
return get_default_chrome_driver_path()
elif browser_type == 'edge':
return get_default_edge_driver_path()
elif browser_type == 'firefox':
return get_default_firefox_driver_path()
elif browser_type == 'brave':
# Brave 使用 Chrome 的 driver
return get_default_chrome_driver_path()
else:
# Default to Chrome if browser type is unknown
return get_default_chrome_driver_path()
def get_default_chrome_driver_path():
"""Get default Chrome driver path"""
def find_executable(executable_names: List[str]) -> Optional[str]:
"""Find executable in PATH by trying multiple possible names.
Args:
executable_names: List of possible executable names to try
Returns:
Path to the executable if found, None otherwise
"""
for name in executable_names:
try:
path = shutil.which(name)
if path:
return path
except Exception:
continue
return None
def get_default_driver_path(browser_type: str = 'chrome') -> str:
"""Get default driver path based on browser type.
Args:
browser_type: Type of browser ('chrome', 'edge', 'firefox', 'brave')
Returns:
str: Path to the browser driver
"""
browser_type = browser_type.lower()
driver_map = {
'chrome': get_default_chrome_driver_path,
'edge': get_default_edge_driver_path,
'firefox': get_default_firefox_driver_path,
'brave': get_default_chrome_driver_path, # Brave uses Chrome driver
'opera': get_default_chrome_driver_path, # Opera uses Chrome driver
'operagx': get_default_chrome_driver_path # OperaGX uses Chrome driver
}
driver_func = driver_map.get(browser_type, get_default_chrome_driver_path)
return driver_func()
def get_default_chrome_driver_path() -> str:
"""Get default Chrome driver path based on platform."""
if sys.platform == "win32":
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "chromedriver.exe")
elif sys.platform == "darwin":
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "chromedriver")
else:
else: # Linux and other Unix-like systems
# Try to find chromedriver in PATH first
path = find_executable(["chromedriver"])
if path:
return path
return "/usr/local/bin/chromedriver"
def get_default_edge_driver_path():
"""Get default Edge driver path"""
def get_default_edge_driver_path() -> str:
"""Get default Edge driver path based on platform."""
if sys.platform == "win32":
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "msedgedriver.exe")
elif sys.platform == "darwin":
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "msedgedriver")
else:
else: # Linux and other Unix-like systems
path = find_executable(["msedgedriver"])
if path:
return path
return "/usr/local/bin/msedgedriver"
def get_default_firefox_driver_path():
"""Get default Firefox driver path"""
def get_default_firefox_driver_path() -> str:
"""Get default Firefox driver path based on platform."""
if sys.platform == "win32":
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "geckodriver.exe")
elif sys.platform == "darwin":
return os.path.join(os.path.dirname(os.path.abspath(__file__)), "drivers", "geckodriver")
else:
else: # Linux and other Unix-like systems
path = find_executable(["geckodriver"])
if path:
return path
return "/usr/local/bin/geckodriver"
def get_default_brave_driver_path():
"""Get default Brave driver path (uses Chrome driver)"""
# Brave 浏览器基于 Chromium所以使用相同的 chromedriver
return get_default_chrome_driver_path()
def get_default_browser_path(browser_type='chrome'):
"""Get default browser executable path"""
def get_default_browser_path(browser_type: str = 'chrome') -> str:
"""Get default browser executable path based on platform and browser type.
Args:
browser_type: Type of browser ('chrome', 'edge', 'firefox', 'brave', 'opera', 'operagx')
Returns:
str: Path to the browser executable
"""
browser_type = browser_type.lower()
# Platform-specific browser paths
if sys.platform == "win32":
if browser_type == 'chrome':
# 尝试在 PATH 中找到 Chrome
try:
import shutil
chrome_in_path = shutil.which("chrome")
if chrome_in_path:
return chrome_in_path
except:
pass
# 使用默认路径
return r"C:\Program Files\Google\Chrome\Application\chrome.exe"
elif browser_type == 'edge':
return r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
elif browser_type == 'firefox':
return r"C:\Program Files\Mozilla Firefox\firefox.exe"
elif browser_type == 'opera':
# 尝试多个可能的 Opera 路径
opera_paths = [
r"C:\Program Files\Opera\opera.exe",
r"C:\Program Files (x86)\Opera\opera.exe",
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera', 'launcher.exe'),
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera', 'opera.exe')
]
for path in opera_paths:
if os.path.exists(path):
return path
return opera_paths[0] # 返回第一个路径,即使它不存在
elif browser_type == 'operagx':
# 尝试多个可能的 Opera GX 路径
operagx_paths = [
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera GX', 'launcher.exe'),
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera GX', 'opera.exe'),
r"C:\Program Files\Opera GX\opera.exe",
r"C:\Program Files (x86)\Opera GX\opera.exe"
]
for path in operagx_paths:
if os.path.exists(path):
return path
return operagx_paths[0] # 返回第一个路径,即使它不存在
elif browser_type == 'brave':
# Brave 浏览器的默认安装路径
paths = [
os.path.join(os.environ.get('PROGRAMFILES', ''), 'BraveSoftware/Brave-Browser/Application/brave.exe'),
os.path.join(os.environ.get('PROGRAMFILES(X86)', ''), 'BraveSoftware/Brave-Browser/Application/brave.exe'),
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'BraveSoftware/Brave-Browser/Application/brave.exe')
]
for path in paths:
if os.path.exists(path):
return path
return paths[0] # 返回第一个路径,即使它不存在
return _get_windows_browser_path(browser_type)
elif sys.platform == "darwin":
if browser_type == 'chrome':
return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
elif browser_type == 'edge':
return "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"
elif browser_type == 'firefox':
return "/Applications/Firefox.app/Contents/MacOS/firefox"
elif browser_type == 'brave':
return "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
elif browser_type == 'opera':
return "/Applications/Opera.app/Contents/MacOS/Opera"
elif browser_type == 'operagx':
return "/Applications/Opera GX.app/Contents/MacOS/Opera"
else: # Linux
if browser_type == 'chrome':
# 尝试多种可能的名称
chrome_names = ["google-chrome", "chrome", "chromium", "chromium-browser"]
for name in chrome_names:
try:
import shutil
path = shutil.which(name)
if path:
return path
except:
pass
return "/usr/bin/google-chrome"
elif browser_type == 'edge':
return "/usr/bin/microsoft-edge"
elif browser_type == 'firefox':
return "/usr/bin/firefox"
elif browser_type == 'opera':
return "/usr/bin/opera"
elif browser_type == 'operagx':
# 尝试常见的 Opera GX 路径
operagx_names = ["opera-gx"]
for name in operagx_names:
try:
import shutil
path = shutil.which(name)
if path:
return path
except:
pass
return "/usr/bin/opera-gx"
elif browser_type == 'brave':
# 尝试常见的 Brave 路径
brave_names = ["brave", "brave-browser"]
for name in brave_names:
try:
import shutil
path = shutil.which(name)
if path:
return path
except:
pass
return "/usr/bin/brave-browser"
# 如果找不到指定的浏览器类型,则返回 Chrome 的路径
return get_default_browser_path('chrome')
return _get_macos_browser_path(browser_type)
else: # Linux and other Unix-like systems
return _get_linux_browser_path(browser_type)
def get_linux_cursor_path():
"""Get Linux Cursor path"""
def _get_windows_browser_path(browser_type: str) -> str:
"""Get browser path for Windows."""
browser_paths = {
'chrome': [
shutil.which("chrome"),
r"C:\Program Files\Google\Chrome\Application\chrome.exe",
r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe",
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Google', 'Chrome', 'Application', 'chrome.exe')
],
'edge': [
shutil.which("msedge"),
r"C:\Program Files\Microsoft\Edge\Application\msedge.exe",
r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe"
],
'firefox': [
shutil.which("firefox"),
r"C:\Program Files\Mozilla Firefox\firefox.exe",
r"C:\Program Files (x86)\Mozilla Firefox\firefox.exe"
],
'opera': [
shutil.which("opera"),
r"C:\Program Files\Opera\opera.exe",
r"C:\Program Files (x86)\Opera\opera.exe",
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera', 'launcher.exe'),
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera', 'opera.exe')
],
'operagx': [
shutil.which("opera"),
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera GX', 'launcher.exe'),
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'Programs', 'Opera GX', 'opera.exe'),
r"C:\Program Files\Opera GX\opera.exe",
r"C:\Program Files (x86)\Opera GX\opera.exe"
],
'brave': [
shutil.which("brave"),
os.path.join(os.environ.get('PROGRAMFILES', ''), 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
os.path.join(os.environ.get('PROGRAMFILES(X86)', ''), 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe'),
os.path.join(os.environ.get('LOCALAPPDATA', ''), 'BraveSoftware', 'Brave-Browser', 'Application', 'brave.exe')
]
}
# Return first existing path
paths = browser_paths.get(browser_type, browser_paths['chrome'])
for path in paths:
if path and os.path.exists(path):
return path
# Return first path as fallback
return next((p for p in paths if p), r"C:\Program Files\Google\Chrome\Application\chrome.exe")
def _get_macos_browser_path(browser_type: str) -> str:
"""Get browser path for macOS."""
browser_paths = {
'chrome': [
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
"~/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
],
'edge': [
"/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
"~/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge"
],
'firefox': [
"/Applications/Firefox.app/Contents/MacOS/firefox",
"~/Applications/Firefox.app/Contents/MacOS/firefox"
],
'brave': [
"/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
"~/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
],
'opera': [
"/Applications/Opera.app/Contents/MacOS/Opera",
"~/Applications/Opera.app/Contents/MacOS/Opera"
],
'operagx': [
"/Applications/Opera GX.app/Contents/MacOS/Opera",
"~/Applications/Opera GX.app/Contents/MacOS/Opera"
]
}
# Return first existing path
paths = browser_paths.get(browser_type, browser_paths['chrome'])
for path in paths:
expanded_path = os.path.expanduser(path)
if os.path.exists(expanded_path):
return expanded_path
# Return first path as fallback
return os.path.expanduser(paths[0])
def _get_linux_browser_path(browser_type: str) -> str:
"""Get browser path for Linux."""
browser_executables = {
'chrome': ["google-chrome", "chrome", "chromium", "chromium-browser"],
'edge': ["microsoft-edge", "msedge"],
'firefox': ["firefox", "firefox-esr"],
'opera': ["opera"],
'operagx': ["opera-gx", "opera"],
'brave': ["brave-browser", "brave"]
}
# Try to find executable in PATH
executables = browser_executables.get(browser_type, browser_executables['chrome'])
path = find_executable(executables)
if path:
return path
# Fallback to common locations
common_locations = {
'chrome': "/usr/bin/google-chrome",
'edge': "/usr/bin/microsoft-edge",
'firefox': "/usr/bin/firefox",
'opera': "/usr/bin/opera",
'operagx': "/usr/bin/opera",
'brave': "/usr/bin/brave-browser"
}
return common_locations.get(browser_type, common_locations['chrome'])
def get_linux_cursor_path() -> str:
"""Get Linux Cursor path by checking multiple possible locations."""
possible_paths = [
"/opt/Cursor/resources/app",
"/usr/share/cursor/resources/app",
"/opt/cursor-bin/resources/app",
"/usr/lib/cursor/resources/app",
os.path.expanduser("~/.local/share/cursor/resources/app")
os.path.expanduser("~/.local/share/cursor/resources/app"),
# Add extracted AppImage paths
*[p for p in [os.path.expanduser("~/squashfs-root/usr/share/cursor/resources/app")] if os.path.exists(p)]
]
# return the first path that exists
return next((path for path in possible_paths if os.path.exists(path)), possible_paths[0])
# Return first existing path or default if none exists
for path in possible_paths:
if os.path.exists(path):
return path
# Log warning if no path found
logger.warning("No Cursor installation found in common Linux paths")
return possible_paths[0]
def get_random_wait_time(config, timing_key):
"""Get random wait time based on configuration timing settings
def parse_time_range(time_str: str) -> Tuple[float, float]:
"""Parse a time range string into min and max values.
Args:
config (dict): Configuration dictionary containing timing settings
timing_key (str): Key to look up in the timing settings
time_str: String representing time range (e.g., "0.5-1.5" or "0.5,1.5")
Returns:
Tuple of (min_time, max_time)
"""
try:
if isinstance(time_str, (int, float)):
return float(time_str), float(time_str)
if '-' in time_str:
min_time, max_time = map(float, time_str.split('-'))
elif ',' in time_str:
min_time, max_time = map(float, time_str.split(','))
else:
min_time = max_time = float(time_str)
return min_time, max_time
except (ValueError, TypeError):
return 0.5, 1.5
def get_random_wait_time(config: Dict, timing_key: str, default_range: Tuple[float, float] = (0.5, 1.5)) -> float:
"""Get random wait time based on configuration timing settings.
Args:
config: Configuration dictionary containing timing settings
timing_key: Key to look up in the timing settings
default_range: Default time range to use if config value is invalid
Returns:
float: Random wait time in seconds
"""
try:
# Get timing value from config
if not config or 'Timing' not in config:
return random.uniform(*default_range)
timing = config.get('Timing', {}).get(timing_key)
if not timing:
# Default to 0.5-1.5 seconds if timing not found
return random.uniform(0.5, 1.5)
# Check if timing is a range (e.g., "0.5-1.5" or "0.5,1.5")
if isinstance(timing, str):
if '-' in timing:
min_time, max_time = map(float, timing.split('-'))
elif ',' in timing:
min_time, max_time = map(float, timing.split(','))
else:
# Single value, use it as both min and max
min_time = max_time = float(timing)
else:
# If timing is a number, use it as both min and max
min_time = max_time = float(timing)
return random.uniform(*default_range)
min_time, max_time = parse_time_range(timing)
return random.uniform(min_time, max_time)
except (ValueError, TypeError, AttributeError):
# Return default value if any error occurs
return random.uniform(0.5, 1.5)
except Exception as e:
logger.warning(f"Error getting wait time for {timing_key}: {e}")
return random.uniform(*default_range)