feat: Refactor Project Structure and Add Configuration Management

- Add `config.py` for centralized configuration management
- Add `utils.py` for cross-platform utility functions
- Remove `browser.py` and `control.py` to simplify project structure
- Update version to 1.7.06
- Modify build specification to reflect new file structure
- Update localization files with new update confirmation messages
- Enhance configuration loading and path detection across different platforms
This commit is contained in:
yeongpin 2025-03-11 11:49:17 +08:00
parent 6ca80ccb10
commit ff358588bb
18 changed files with 303 additions and 484 deletions

4
.env
View File

@ -1,2 +1,2 @@
version=1.7.05 version=1.7.06
VERSION=1.7.05 VERSION=1.7.06

View File

@ -6,7 +6,7 @@ on:
version: version:
description: 'Version number (e.g. 1.0.9)' description: 'Version number (e.g. 1.0.9)'
required: true required: true
default: '1.7.05' default: '1.7.06'
permissions: permissions:
contents: write contents: write

View File

@ -1,5 +1,19 @@
# Change Log # Change Log
## v1.7.06
1. Add: Update Confirm | 增加更新確認
2. Add: Update Skipped | 增加更新跳過
3. Add: Invalid Choice | 增加無效選擇
4. Fix: Cursor Path | 修復Cursor路徑
5. Fix: Path Encoding | 修復路徑編碼
6. Fix: Getting Verification Code | 修復獲取驗證碼
7. Fix: Setting Password | 修復設置密碼
8. Fix: Disable Auto Update | 修復禁用自動更新
9. Add Config.py | 增加Config.py
10. Add utils.py | 增加utils.py
11. Rebuild some logic | 重新構建一些邏輯
## v1.7.05 ## v1.7.05
1. Fix: Cursor Version Check | 修復Cursor版本檢查 1. Fix: Cursor Version Check | 修復Cursor版本檢查
2. Fix: Small Problem | 修復一些小問題 2. Fix: Small Problem | 修復一些小問題

View File

@ -1,95 +0,0 @@
from DrissionPage import ChromiumOptions, ChromiumPage
import sys
import os
import logging
import random
class BrowserManager:
def __init__(self, noheader=False):
self.browser = None
self.noheader = noheader
def init_browser(self):
"""初始化浏览器"""
co = self._get_browser_options()
# 如果设置了 noheader添加相应的参数
if self.noheader:
co.set_argument('--headless=new')
self.browser = ChromiumPage(co)
return self.browser
def _get_browser_options(self):
"""获取浏览器配置"""
co = ChromiumOptions()
try:
extension_path = self._get_extension_path()
co.add_extension(extension_path)
co.set_argument("--allow-extensions-in-incognito")
extension_block_path = self.get_extension_block()
co.add_extension(extension_block_path)
co.set_argument("--allow-extensions-in-incognito")
except FileNotFoundError as e:
logging.warning(f"警告: {e}")
# 设置更真实的用户代理
co.set_user_agent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
)
# 基本设置
co.set_pref("credentials_enable_service", False)
# 随机端口
co.auto_port()
# 系统特定设置
if sys.platform == "darwin": # macOS
co.set_argument("--disable-gpu")
co.set_argument("--no-sandbox")
elif sys.platform == "win32": # Windows
co.set_argument("--disable-software-rasterizer")
# 设置窗口大小
window_width = random.randint(1024, 1920)
window_height = random.randint(768, 1080)
co.set_argument(f"--window-size={window_width},{window_height}")
return co
def _get_extension_path(self):
"""获取插件路径"""
root_dir = os.getcwd()
extension_path = os.path.join(root_dir, "turnstilePatch")
if hasattr(sys, "_MEIPASS"):
extension_path = os.path.join(sys._MEIPASS, "turnstilePatch")
if not os.path.exists(extension_path):
raise FileNotFoundError(f"插件不存在: {extension_path}")
return extension_path
def get_extension_block(self):
"""获取插件路径"""
root_dir = os.getcwd()
extension_path = os.path.join(root_dir, "PBlock")
if hasattr(sys, "_MEIPASS"):
extension_path = os.path.join(sys._MEIPASS, "PBlock")
if not os.path.exists(extension_path):
raise FileNotFoundError(f"插件不存在: {extension_path}")
return extension_path
def quit(self):
"""关闭浏览器"""
if self.browser:
try:
self.browser.quit()
except:
pass

View File

@ -29,15 +29,19 @@ a = Analysis(
('cursor_auth.py', '.'), ('cursor_auth.py', '.'),
('reset_machine_manual.py', '.'), ('reset_machine_manual.py', '.'),
('cursor_register.py', '.'), ('cursor_register.py', '.'),
('browser.py', '.'), ('new_signup.py', '.'),
('control.py', '.'), ('new_tempemail.py', '.'),
('quit_cursor.py', '.'),
('cursor_register_manual.py', '.'),
('.env', '.') ('.env', '.')
], ],
hiddenimports=[ hiddenimports=[
'cursor_auth', 'cursor_auth',
'reset_machine_manual', 'reset_machine_manual',
'browser', 'new_signup',
'control' 'new_tempemail',
'quit_cursor',
'cursor_register_manual'
], ],
hookspath=[], hookspath=[],
hooksconfig={}, hooksconfig={},

117
config.py Normal file
View File

@ -0,0 +1,117 @@
import os
import sys
import configparser
from colorama import Fore, Style
from utils import get_user_documents_path, get_default_chrome_path, get_linux_cursor_path
def setup_config(translator=None):
"""Setup configuration file and return config object"""
try:
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
os.makedirs(config_dir, exist_ok=True)
config = configparser.ConfigParser()
# 默認配置
default_config = {
'Chrome': {
'chromepath': get_default_chrome_path()
},
'Turnstile': {
'handle_turnstile_time': '2',
'handle_turnstile_random_time': '1-3'
},
'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'
}
}
# 添加系統特定路徑配置
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")
}
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")
}
elif sys.platform == "linux":
sudo_user = os.environ.get('SUDO_USER')
actual_home = f"/home/{sudo_user}" if sudo_user else os.path.expanduser("~")
default_config['LinuxPaths'] = {
'storage_path': os.path.abspath(os.path.join(actual_home, ".config/Cursor/User/globalStorage/storage.json")),
'sqlite_path': os.path.abspath(os.path.join(actual_home, ".config/Cursor/User/globalStorage/state.vscdb")),
'machine_id_path': os.path.expanduser("~/.config/Cursor/machineId"),
'cursor_path': get_linux_cursor_path(),
'updater_path': os.path.expanduser("~/.config/cursor-updater")
}
# 讀取現有配置並合併
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} {translator.get('register.config_option_added', option=f'{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}{translator.get('register.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}{translator.get('register.config_created')}: {config_file}{Style.RESET_ALL}")
return config
except Exception as e:
if translator:
print(f"{Fore.RED}{translator.get('register.config_setup_error', error=str(e))}{Style.RESET_ALL}")
else:
print(f"{Fore.RED}❌ Error setting up config: {e}{Style.RESET_ALL}")
return None
def get_config(translator=None):
"""Get existing config or create new one"""
return setup_config(translator)

View File

@ -1,181 +0,0 @@
import time
import random
import os
from colorama import Fore, Style, init
# 初始化colorama
init()
# 定义emoji常量
EMOJI = {
'MAIL': '📧',
'REFRESH': '🔄',
'SUCCESS': '',
'ERROR': '',
'INFO': '',
'CODE': '📱'
}
class BrowserControl:
def __init__(self, browser, translator=None):
self.browser = browser
self.translator = translator # 保存translator
self.sign_up_url = "https://authenticator.cursor.sh/sign-up"
self.current_tab = None # 当前标签页
self.signup_tab = None # 注册标签页
self.email_tab = None # 邮箱标签页
def create_new_tab(self):
"""创建新标签页"""
try:
# 保存当前标签页
self.current_tab = self.browser
# 创建新的浏览器实例
from browser import BrowserManager
browser_manager = BrowserManager()
new_browser = browser_manager.init_browser()
# 保存新标签页
self.signup_tab = new_browser
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.create_new_tab_success')}{Style.RESET_ALL}")
return new_browser
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.create_new_tab_failed', error=str(e))}{Style.RESET_ALL}")
return None
def fill_verification_code(self, code):
"""填写验证码"""
try:
if not code or len(code) != 6:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.verification_code_format_error')}{Style.RESET_ALL}")
return False
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.fill_verification_code')}...{Style.RESET_ALL}")
# 记住当前标签页(邮箱页面)
email_tab = self.browser
# 切换回注册页面标签
self.switch_to_tab(self.signup_tab)
time.sleep(1)
# 输入验证码
for digit in code:
self.browser.actions.input(digit)
time.sleep(random.uniform(0.1, 0.3))
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.verification_code_filled')}{Style.RESET_ALL}")
# 等待页面加载和登录完成
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.wait_for_login')}...{Style.RESET_ALL}")
time.sleep(5)
# 先访问登录页面确保登录状态
login_url = "https://authenticator.cursor.sh"
self.browser.get(login_url)
time.sleep(3) # 增加等待时间
# 获取cookies第一次尝试
token = self.get_cursor_session_token()
if not token:
print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.get_token_failed')}...{Style.RESET_ALL}")
time.sleep(3)
token = self.get_cursor_session_token()
if token:
self.save_token_to_file(token)
# 获取到token后再访问设置页面
settings_url = "https://www.cursor.com/settings"
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.get_account_info')}...{Style.RESET_ALL}")
self.browser.get(settings_url)
time.sleep(2)
# 获取账户额度信息
try:
usage_selector = (
"css:div.col-span-2 > div > div > div > div > "
"div:nth-child(1) > div.flex.items-center.justify-between.gap-2 > "
"span.font-mono.text-sm\\/\\[0\\.875rem\\]"
)
usage_ele = self.browser.ele(usage_selector)
if usage_ele:
usage_info = usage_ele.text
total_usage = usage_info.split("/")[-1].strip()
print(f"{Fore.GREEN}{EMOJI['INFO']} {self.translator.get('control.account_usage_limit')}: {total_usage}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.get_account_usage_failed', error=str(e))}{Style.RESET_ALL}")
# 切换回邮箱页面
self.switch_to_tab(email_tab)
return True
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.fill_verification_code_failed', error=str(e))}{Style.RESET_ALL}")
return False
def check_and_click_turnstile(self):
"""检查并点击 Turnstile 验证框"""
try:
# 等待验证框出现
time.sleep(1)
# 查找验证框
verify_checkbox = self.browser.ele('xpath://label[contains(@class, "cb-lb")]//input[@type="checkbox"]')
if verify_checkbox:
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.find_turnstile_verification_box')}...{Style.RESET_ALL}")
verify_checkbox.click()
time.sleep(2) # 等待验证完成
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.clicked_turnstile_verification_box')}{Style.RESET_ALL}")
return True
return False
except Exception as e:
print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.check_and_click_turnstile_failed', error=str(e))}{Style.RESET_ALL}")
return False
def get_cursor_session_token(self, max_attempts=3, retry_interval=2):
"""获取Cursor会话token"""
print(f"{Fore.CYAN}{EMOJI['INFO']} {self.translator.get('control.get_cursor_session_token')}...{Style.RESET_ALL}")
attempts = 0
while attempts < max_attempts:
try:
# 直接从浏览器对象获取cookies
all_cookies = self.browser.get_cookies()
# 遍历查找目标cookie
for cookie in all_cookies:
if cookie.get("name") == "WorkosCursorSessionToken":
token = cookie["value"].split("%3A%3A")[1]
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.get_cursor_session_token_success')}: {token}{Style.RESET_ALL}")
return token
attempts += 1
if attempts < max_attempts:
print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.get_cursor_session_token_failed', attempts=attempts, retry_interval=retry_interval)}...{Style.RESET_ALL}")
time.sleep(retry_interval)
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.reach_max_attempts', max_attempts=max_attempts)}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.get_cookie_failed', error=str(e))}{Style.RESET_ALL}")
attempts += 1
if attempts < max_attempts:
print(f"{Fore.YELLOW}{EMOJI['ERROR']} {self.translator.get('control.will_retry_in', retry_interval=retry_interval)}...{Style.RESET_ALL}")
time.sleep(retry_interval)
return None
def save_token_to_file(self, token):
"""保存token到文件"""
try:
with open('cursor_tokens.txt', 'a', encoding='utf-8') as f:
f.write(f"Token: {token}\n")
f.write("-" * 50 + "\n")
print(f"{Fore.GREEN}{EMOJI['SUCCESS']} {self.translator.get('control.token_saved_to_file')}{Style.RESET_ALL}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('control.save_token_failed', error=str(e))}{Style.RESET_ALL}")

View File

@ -2,6 +2,7 @@ import sqlite3
import os import os
import sys import sys
from colorama import Fore, Style, init from colorama import Fore, Style, init
from config import get_config
# 初始化colorama # 初始化colorama
init() init()
@ -21,21 +22,40 @@ EMOJI = {
class CursorAuth: class CursorAuth:
def __init__(self, translator=None): def __init__(self, translator=None):
self.translator = translator self.translator = translator
# 判断操作系统
if sys.platform == "win32": # Windows # 获取配置
self.db_path = os.path.join( config = get_config(translator)
os.getenv("APPDATA"), "Cursor", "User", "globalStorage", "state.vscdb" if not config:
) print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.config_error') if self.translator else 'Failed to load configuration'}{Style.RESET_ALL}")
elif sys.platform == 'linux': sys.exit(1)
self.db_path = os.path.expanduser(
"~/.config/Cursor/User/globalStorage/state.vscdb" # 根据操作系统获取路径
) try:
elif sys.platform == 'darwin': # macOS if sys.platform == "win32": # Windows
self.db_path = os.path.expanduser( if not config.has_section('WindowsPaths'):
"~/Library/Application Support/Cursor/User/globalStorage/state.vscdb" raise ValueError("Windows paths not configured")
) self.db_path = config.get('WindowsPaths', 'sqlite_path')
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.unsupported_platform')}{Style.RESET_ALL}") elif sys.platform == 'linux': # Linux
if not config.has_section('LinuxPaths'):
raise ValueError("Linux paths not configured")
self.db_path = config.get('LinuxPaths', 'sqlite_path')
elif sys.platform == 'darwin': # macOS
if not config.has_section('MacPaths'):
raise ValueError("macOS paths not configured")
self.db_path = config.get('MacPaths', 'sqlite_path')
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.unsupported_platform') if self.translator else 'Unsupported platform'}{Style.RESET_ALL}")
sys.exit(1)
# 验证路径是否存在
if not os.path.exists(os.path.dirname(self.db_path)):
raise FileNotFoundError(f"Database directory not found: {os.path.dirname(self.db_path)}")
except Exception as e:
print(f"{Fore.RED}{EMOJI['ERROR']} {self.translator.get('auth.path_error', error=str(e)) if self.translator else f'Error getting database path: {str(e)}'}{Style.RESET_ALL}")
sys.exit(1) sys.exit(1)
# 检查数据库文件是否存在 # 检查数据库文件是否存在

View File

@ -2,8 +2,6 @@ import os
from colorama import Fore, Style, init from colorama import Fore, Style, init
import time import time
import random import random
from browser import BrowserManager
from control import BrowserControl
from cursor_auth import CursorAuth from cursor_auth import CursorAuth
from reset_machine_manual import MachineIDResetter from reset_machine_manual import MachineIDResetter
@ -35,7 +33,6 @@ class CursorRegistration:
self.translator = translator self.translator = translator
# Set to display mode # Set to display mode
os.environ['BROWSER_HEADLESS'] = 'False' os.environ['BROWSER_HEADLESS'] = 'False'
self.browser_manager = BrowserManager()
self.browser = None self.browser = None
self.controller = None self.controller = None
self.mail_url = "https://yopmail.com/zh/email-generator" self.mail_url = "https://yopmail.com/zh/email-generator"

View File

@ -2,8 +2,6 @@ import os
from colorama import Fore, Style, init from colorama import Fore, Style, init
import time import time
import random import random
from browser import BrowserManager
from control import BrowserControl
from cursor_auth import CursorAuth from cursor_auth import CursorAuth
from reset_machine_manual import MachineIDResetter from reset_machine_manual import MachineIDResetter
@ -35,7 +33,6 @@ class CursorRegistration:
self.translator = translator self.translator = translator
# Set to display mode # Set to display mode
os.environ['BROWSER_HEADLESS'] = 'False' os.environ['BROWSER_HEADLESS'] = 'False'
self.browser_manager = BrowserManager()
self.browser = None self.browser = None
self.controller = None self.controller = None
self.sign_up_url = "https://authenticator.cursor.sh/sign-up" self.sign_up_url = "https://authenticator.cursor.sh/sign-up"

View File

@ -4,6 +4,7 @@ import platform
import shutil import shutil
from colorama import Fore, Style, init from colorama import Fore, Style, init
import subprocess import subprocess
from config import get_config
# Initialize colorama # Initialize colorama
init() init()
@ -24,11 +25,24 @@ class AutoUpdateDisabler:
def __init__(self, translator=None): def __init__(self, translator=None):
self.translator = translator self.translator = translator
self.system = platform.system() self.system = platform.system()
self.updater_paths = {
"Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"), # 从配置文件获取路径
"Darwin": os.path.expanduser("~/Library/Application Support/cursor-updater"), config = get_config(translator)
"Linux": os.path.expanduser("~/.config/cursor-updater") if config:
} if self.system == "Windows":
self.updater_path = config.get('WindowsPaths', 'updater_path', fallback=os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"))
elif self.system == "Darwin":
self.updater_path = config.get('MacPaths', 'updater_path', fallback=os.path.expanduser("~/Library/Application Support/cursor-updater"))
elif self.system == "Linux":
self.updater_path = config.get('LinuxPaths', 'updater_path', fallback=os.path.expanduser("~/.config/cursor-updater"))
else:
# 如果配置加载失败,使用默认路径
self.updater_paths = {
"Windows": os.path.join(os.getenv("LOCALAPPDATA", ""), "cursor-updater"),
"Darwin": os.path.expanduser("~/Library/Application Support/cursor-updater"),
"Linux": os.path.expanduser("~/.config/cursor-updater")
}
self.updater_path = self.updater_paths.get(self.system)
def _kill_cursor_processes(self): def _kill_cursor_processes(self):
"""End all Cursor processes""" """End all Cursor processes"""
@ -50,7 +64,7 @@ class AutoUpdateDisabler:
def _remove_updater_directory(self): def _remove_updater_directory(self):
"""Delete updater directory""" """Delete updater directory"""
try: try:
updater_path = self.updater_paths.get(self.system) updater_path = self.updater_path
if not updater_path: if not updater_path:
raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}") raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")
@ -72,7 +86,7 @@ class AutoUpdateDisabler:
def _create_blocking_file(self): def _create_blocking_file(self):
"""Create blocking file""" """Create blocking file"""
try: try:
updater_path = self.updater_paths.get(self.system) updater_path = self.updater_path
if not updater_path: if not updater_path:
raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}") raise OSError(self.translator.get('update.unsupported_os', system=self.system) if self.translator else f"不支持的操作系统: {self.system}")

View File

@ -160,7 +160,9 @@
"config_option_added": "Config Option Added: {option}", "config_option_added": "Config Option Added: {option}",
"config_updated": "Config Updated", "config_updated": "Config Updated",
"password_submitted": "Password Submitted", "password_submitted": "Password Submitted",
"total_usage": "Total Usage: {usage}" "total_usage": "Total Usage: {usage}",
"setting_on_password": "Setting Password",
"getting_code": "Getting Verification Code, Will Try in 60s"
}, },
"auth": { "auth": {
"title": "Cursor Auth Manager", "title": "Cursor Auth Manager",
@ -270,6 +272,9 @@
"updating": "Updating to the latest version. The program will restart automatically.", "updating": "Updating to the latest version. The program will restart automatically.",
"up_to_date": "You are using the latest version.", "up_to_date": "You are using the latest version.",
"check_failed": "Failed to check for updates: {error}", "check_failed": "Failed to check for updates: {error}",
"continue_anyway": "Continuing with current version..." "continue_anyway": "Continuing with current version...",
"update_confirm": "Do you want to update to the latest version? (Y/n)",
"update_skipped": "Skipping update.",
"invalid_choice": "Invalid choice. Please enter 'Y' or 'n'."
} }
} }

View File

@ -159,7 +159,9 @@
"config_option_added": "配置项已添加: {option}", "config_option_added": "配置项已添加: {option}",
"config_updated": "配置已更新", "config_updated": "配置已更新",
"password_submitted": "密码已提交", "password_submitted": "密码已提交",
"total_usage": "总使用量: {usage}" "total_usage": "总使用量: {usage}",
"setting_on_password": "设置密码",
"getting_code": "获取验证码将在60秒内尝试..."
}, },
"auth": { "auth": {
"title": "Cursor 认证管理器", "title": "Cursor 认证管理器",
@ -266,6 +268,9 @@
"updating": "正在更新到最新版本。程序将自动重启。", "updating": "正在更新到最新版本。程序将自动重启。",
"up_to_date": "您使用的是最新版本。", "up_to_date": "您使用的是最新版本。",
"check_failed": "检查更新失败: {error}", "check_failed": "检查更新失败: {error}",
"continue_anyway": "继续使用当前版本..." "continue_anyway": "继续使用当前版本...",
"update_confirm": "是否要更新到最新版本? (Y/n)",
"update_skipped": "跳过更新。",
"invalid_choice": "选择无效。请输入 'Y' 或 'n'."
} }
} }

View File

@ -141,7 +141,9 @@
"config_option_added": "配置項已添加: {option}", "config_option_added": "配置項已添加: {option}",
"config_updated": "配置已更新", "config_updated": "配置已更新",
"password_submitted": "密碼已提交", "password_submitted": "密碼已提交",
"total_usage": "總使用量: {usage}" "total_usage": "總使用量: {usage}",
"setting_on_password": "設置密碼",
"getting_code": "正在獲取驗證碼將在60秒內嘗試..."
}, },
"auth": { "auth": {
"title": "Cursor 認證管理器", "title": "Cursor 認證管理器",
@ -248,6 +250,9 @@
"updating": "正在更新到最新版本。程序將自動重啟。", "updating": "正在更新到最新版本。程序將自動重啟。",
"up_to_date": "您使用的是最新版本。", "up_to_date": "您使用的是最新版本。",
"check_failed": "檢查更新失敗: {error}", "check_failed": "檢查更新失敗: {error}",
"continue_anyway": "繼續使用當前版本..." "continue_anyway": "繼續使用當前版本...",
"update_confirm": "是否要更新到最新版本? (Y/n)",
"update_skipped": "跳過更新。",
"invalid_choice": "選擇無效。請輸入 'Y' 或 'n'."
} }
} }

15
main.py
View File

@ -9,6 +9,7 @@ import locale
import platform import platform
import requests import requests
import subprocess import subprocess
from config import get_config
# 只在 Windows 系统上导入 windll # 只在 Windows 系统上导入 windll
if platform.system() == 'Windows': if platform.system() == 'Windows':
@ -238,6 +239,17 @@ def check_latest_version():
if latest_version != version: if latest_version != version:
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.new_version_available', current=version, latest=latest_version)}{Style.RESET_ALL}") print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.new_version_available', current=version, latest=latest_version)}{Style.RESET_ALL}")
# 詢問用戶是否要更新
while True:
choice = input(f"\n{EMOJI['ARROW']} {Fore.CYAN}{translator.get('updater.update_confirm', choices='Y/n')}: {Style.RESET_ALL}").lower()
if choice in ['', 'y', 'yes']:
break
elif choice in ['n', 'no']:
print(f"\n{Fore.YELLOW}{EMOJI['INFO']} {translator.get('updater.update_skipped')}{Style.RESET_ALL}")
return
else:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.invalid_choice')}{Style.RESET_ALL}")
try: try:
# Execute update command based on platform # Execute update command based on platform
if platform.system() == 'Windows': if platform.system() == 'Windows':
@ -287,8 +299,7 @@ def main():
print_logo() print_logo()
# 初始化配置 # 初始化配置
from new_signup import setup_config config = get_config(translator)
config = setup_config(translator)
if not config: if not config:
print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.config_init_failed')}{Style.RESET_ALL}") print(f"{Fore.RED}{EMOJI['ERROR']} {translator.get('menu.config_init_failed')}{Style.RESET_ALL}")
return return

View File

@ -7,6 +7,7 @@ from colorama import Fore, Style
import configparser import configparser
from pathlib import Path from pathlib import Path
import sys import sys
from config import get_config
# 在文件开头添加全局变量 # 在文件开头添加全局变量
_translator = None _translator = None
@ -161,120 +162,11 @@ def get_random_wait_time(config, timing_type='page_load_wait'):
except: except:
return random.uniform(0.1, 0.8) # 出错时返回默认值 return random.uniform(0.1, 0.8) # 出错时返回默认值
def setup_config(translator=None):
"""Setup configuration file and return config object"""
try:
# Set configuration file path
config_dir = os.path.join(get_user_documents_path(), ".cursor-free-vip")
config_file = os.path.join(config_dir, "config.ini")
# Create config directory (if it doesn't exist)
os.makedirs(config_dir, exist_ok=True)
# Read or create configuration file
config = configparser.ConfigParser()
# 默认配置
default_config = {
'Chrome': {
'chromepath': get_default_chrome_path()
},
'Turnstile': {
'handle_turnstile_time': '2',
'handle_turnstile_random_time': '1-3'
},
'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' # 最大超时时间
}
}
# Add OS-specific path configurations
if sys.platform == "win32":
appdata = os.getenv("APPDATA")
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(os.getenv("APPDATA"), "Cursor", "machineId"),
'cursor_path': os.path.join(os.getenv("LOCALAPPDATA", ""), "Programs", "Cursor", "resources", "app")
}
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"
}
elif sys.platform == "linux":
sudo_user = os.environ.get('SUDO_USER')
actual_home = f"/home/{sudo_user}" if sudo_user else os.path.expanduser("~")
default_config['LinuxPaths'] = {
'storage_path': os.path.abspath(os.path.join(actual_home, ".config/Cursor/User/globalStorage/storage.json")),
'sqlite_path': os.path.abspath(os.path.join(actual_home, ".config/Cursor/User/globalStorage/state.vscdb")),
'machine_id_path': os.path.expanduser("~/.config/Cursor/machineId"),
'cursor_path': "/opt/Cursor/resources/app" # 默認路徑
}
if os.path.exists(config_file):
config.read(config_file)
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, value)
config_modified = True
if translator:
print(f"{Fore.YELLOW} {translator.get('register.config_option_added', option=f'{section}.{option}') if translator else f'添加配置项: {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}{translator.get('register.config_updated') if translator else '配置文件已更新'}{Style.RESET_ALL}")
else:
# 创建新配置文件
config = configparser.ConfigParser()
for section, options in default_config.items():
config.add_section(section)
for option, value in options.items():
config.set(section, option, value)
with open(config_file, 'w', encoding='utf-8') as f:
config.write(f)
if translator:
print(f"{Fore.GREEN}{translator.get('register.config_created') if translator else '已创建配置文件'}: {config_file}{Style.RESET_ALL}")
return config
except Exception as e:
if translator:
print(f"{Fore.RED}{translator.get('register.config_setup_error', error=str(e)) if translator else f'配置设置出错: {str(e)}'}{Style.RESET_ALL}")
raise
def setup_driver(translator=None): def setup_driver(translator=None):
"""Setup browser driver""" """Setup browser driver"""
try: try:
# 获取配置 # 获取配置
config = setup_config(translator) config = get_config(translator)
# Get Chrome path # Get Chrome path
chrome_path = config.get('Chrome', 'chromepath', fallback=get_default_chrome_path()) chrome_path = config.get('Chrome', 'chromepath', fallback=get_default_chrome_path())
@ -449,57 +341,39 @@ def generate_password(length=12):
chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*" chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
return ''.join(random.choices(chars, k=length)) return ''.join(random.choices(chars, k=length))
def fill_password(page, password: str, config, translator=None) -> bool: def fill_password(page, password: str, config, translator=None):
""" """
填写密码表单 填写密码表单
""" """
try: try:
print(f"{Fore.CYAN}🔑 {translator.get('register.setting_password') if translator else '设置密码'}{Style.RESET_ALL}") print(f"{Fore.CYAN}🔑 {translator.get('register.setting_password') if translator else '设置密码'}{Style.RESET_ALL}")
# 等待密码框出现并尝试多次 # 填写密码
max_retries = 5 password_input = page.ele("@name=password")
for i in range(max_retries): print(f"{Fore.CYAN}🔑 {translator.get('register.setting_on_password')}: {password}{Style.RESET_ALL}")
# 检查是否出现错误信息 if password_input:
if page.ele("This email is not available."): password_input.input(password)
print(f"{Fore.RED}{translator.get('register.email_used') if translator else '注册失败:邮箱已被使用'}{Style.RESET_ALL}")
return False
# 查找密码输入框 # 点击提交按钮
password_input = page.ele("@name=password") submit_button = page.ele("@type=submit")
if password_input: if submit_button:
# 清除可能存在的旧值并输入新密码 submit_button.click()
password_input.click() time.sleep(get_random_wait_time(config, 'submit_wait'))
time.sleep(get_random_wait_time(config, 'input_wait'))
password_input.input(password)
time.sleep(get_random_wait_time(config, 'input_wait'))
# 查找并点击提交按钮
submit_button = page.ele("@type=submit")
if submit_button:
submit_button.click()
print(f"{Fore.GREEN}{translator.get('register.password_submitted') if translator else '密码已提交'}{Style.RESET_ALL}")
time.sleep(get_random_wait_time(config, 'submit_wait'))
return True
else:
print(f"{Fore.YELLOW}⚠️ {translator.get('register.retry_submit') if translator else '未找到提交按钮,重试中...'}{Style.RESET_ALL}")
# 如果没找到密码框,等待后重试 print(f"{Fore.GREEN}{translator.get('register.password_submitted') if translator else '密码已提交'}{Style.RESET_ALL}")
time.sleep(get_random_wait_time(config, 'failed_retry_time'))
if i < max_retries - 1: # 不是最后一次尝试时才打印 return True
print(f"{Fore.YELLOW}⚠️ {translator.get('register.retry_password', attempt=i+1) if translator else f'{i+1} 次尝试设置密码...'}{Style.RESET_ALL}")
print(f"{Fore.RED}{translator.get('register.password_set_failed') if translator else '密码设置失败:超过重试次数'}{Style.RESET_ALL}")
return False
except Exception as e: except Exception as e:
print(f"{Fore.RED}{translator.get('register.password_error', error=str(e)) if translator else f'设置密码时出错: {str(e)}'}{Style.RESET_ALL}") print(f"{Fore.RED}{translator.get('register.password_error', error=str(e)) if translator else f'设置密码时出错: {str(e)}'}{Style.RESET_ALL}")
return False return False
def handle_verification_code(browser_tab, email_tab, controller, email, password, config, translator=None): def handle_verification_code(browser_tab, email_tab, controller, config, translator=None):
"""处理验证码""" """处理验证码"""
try: try:
if translator: if translator:
print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}") print(f"\n{Fore.CYAN}🔄 {translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}")
# 检查是否使用手动输入验证码 # 检查是否使用手动输入验证码
if hasattr(controller, 'get_verification_code') and email_tab is None: # 手动模式 if hasattr(controller, 'get_verification_code') and email_tab is None: # 手动模式
@ -520,7 +394,7 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password
time.sleep(get_random_wait_time(config, 'verification_retry_wait')) time.sleep(get_random_wait_time(config, 'verification_retry_wait'))
# 访问设置页面 # 访问设置页面
print(f"{Fore.CYAN} {translator.get('register.visiting_url')}: https://www.cursor.com/settings{Style.RESET_ALL}") print(f"{Fore.CYAN}🔑 {translator.get('register.visiting_url')}: https://www.cursor.com/settings{Style.RESET_ALL}")
browser_tab.get("https://www.cursor.com/settings") browser_tab.get("https://www.cursor.com/settings")
time.sleep(get_random_wait_time(config, 'settings_page_load_wait')) time.sleep(get_random_wait_time(config, 'settings_page_load_wait'))
return True, browser_tab return True, browser_tab
@ -529,7 +403,7 @@ def handle_verification_code(browser_tab, email_tab, controller, email, password
# 自动获取验证码逻辑 # 自动获取验证码逻辑
elif email_tab: elif email_tab:
print(f"{translator.get('register.waiting_for_verification_code')}") print(f"{Fore.CYAN}🔄 {translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}")
time.sleep(get_random_wait_time(config, 'email_check_initial_wait')) time.sleep(get_random_wait_time(config, 'email_check_initial_wait'))
# 使用已有的 email_tab 刷新邮箱 # 使用已有的 email_tab 刷新邮箱
@ -703,13 +577,11 @@ def main(email=None, password=None, first_name=None, last_name=None, email_tab=N
# 访问注册页面 # 访问注册页面
url = "https://authenticator.cursor.sh/sign-up" url = "https://authenticator.cursor.sh/sign-up"
if translator:
print(f"\n{Fore.CYAN}{translator.get('register.visiting_url')}: {url}{Style.RESET_ALL}")
# 访问页面 # 访问页面
simulate_human_input(page, url, config, translator) simulate_human_input(page, url, config, translator)
if translator: if translator:
print(f"{Fore.CYAN}{translator.get('register.waiting_for_page_load')}{Style.RESET_ALL}") print(f"{Fore.CYAN}🔄 {translator.get('register.waiting_for_page_load')}{Style.RESET_ALL}")
time.sleep(get_random_wait_time(config, 'page_load_wait')) time.sleep(get_random_wait_time(config, 'page_load_wait'))
# 如果没有提供账号信息,则生成随机信息 # 如果没有提供账号信息,则生成随机信息
@ -729,34 +601,33 @@ def main(email=None, password=None, first_name=None, last_name=None, email_tab=N
# 填写表单 # 填写表单
if fill_signup_form(page, first_name, last_name, email, config, translator): if fill_signup_form(page, first_name, last_name, email, config, translator):
if translator: if translator:
print(f"\n{Fore.GREEN}{translator.get('register.form_submitted')}{Style.RESET_ALL}") print(f"\n{Fore.GREEN}{translator.get('register.form_submitted')}{Style.RESET_ALL}")
# 处理第一次 Turnstile 验证 # 处理第一次 Turnstile 验证
if handle_turnstile(page, config, translator): if handle_turnstile(page, config, translator):
if translator: if translator:
print(f"\n{Fore.GREEN}{translator.get('register.first_verification_passed')}{Style.RESET_ALL}") print(f"\n{Fore.GREEN}{translator.get('register.first_verification_passed')}{Style.RESET_ALL}")
# 填写密码 # 填写密码
if fill_password(page, password, config, translator): if fill_password(page, password, config, translator):
if translator: if translator:
print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_second_verification')}{Style.RESET_ALL}") print(f"\n{Fore.CYAN}🔄 {translator.get('register.waiting_for_second_verification')}{Style.RESET_ALL}")
time.sleep(2)
# 处理第二次 Turnstile 验证 # 处理第二次 Turnstile 验证
if handle_turnstile(page, config, translator): if handle_turnstile(page, config, translator):
if translator: if translator:
print(f"\n{Fore.CYAN}{translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}") print(f"\n{Fore.CYAN}🔄 {translator.get('register.waiting_for_verification_code')}{Style.RESET_ALL}")
if handle_verification_code(page, email_tab, controller, email, password, config, translator): if handle_verification_code(page, email_tab, controller, config, translator):
success = True success = True
return True, page return True, page
else: else:
print(f"\n{Fore.RED} {translator.get('register.verification_code_processing_failed') if translator else '验证码处理失败'}{Style.RESET_ALL}") print(f"\n{Fore.RED} {translator.get('register.verification_code_processing_failed') if translator else '验证码处理失败'}{Style.RESET_ALL}")
else: else:
print(f"\n{Fore.RED} {translator.get('register.second_verification_failed') if translator else '第二次验证失败'}{Style.RESET_ALL}") print(f"\n{Fore.RED} {translator.get('register.second_verification_failed') if translator else '第二次验证失败'}{Style.RESET_ALL}")
else: else:
print(f"\n{Fore.RED} {translator.get('register.second_verification_failed') if translator else '第二次验证失败'}{Style.RESET_ALL}") print(f"\n{Fore.RED} {translator.get('register.second_verification_failed') if translator else '第二次验证失败'}{Style.RESET_ALL}")
else: else:
print(f"\n{Fore.RED} {translator.get('register.first_verification_failed') if translator else '第一次验证失败'}{Style.RESET_ALL}") print(f"\n{Fore.RED} {translator.get('register.first_verification_failed') if translator else '第一次验证失败'}{Style.RESET_ALL}")
return False, None return False, None

View File

@ -13,6 +13,7 @@ from typing import Tuple
import configparser import configparser
from new_signup import get_user_documents_path from new_signup import get_user_documents_path
import traceback import traceback
from config import get_config
# Initialize colorama # Initialize colorama
init() init()
@ -39,7 +40,7 @@ def get_cursor_paths(translator=None) -> Tuple[str, str]:
if not os.path.exists(config_file): if not os.path.exists(config_file):
raise OSError(translator.get('reset.config_not_found') if translator else "找不到配置文件") raise OSError(translator.get('reset.config_not_found') if translator else "找不到配置文件")
config.read(config_file) config.read(config_file, encoding='utf-8') # 指定編碼
# 根據系統獲取路徑 # 根據系統獲取路徑
if system == "Darwin": if system == "Darwin":
@ -408,7 +409,7 @@ class MachineIDResetter:
if not os.path.exists(config_file): if not os.path.exists(config_file):
raise FileNotFoundError(f"Config file not found: {config_file}") raise FileNotFoundError(f"Config file not found: {config_file}")
config.read(config_file) config.read(config_file, encoding='utf-8')
# Check operating system # Check operating system
if sys.platform == "win32": # Windows if sys.platform == "win32": # Windows
@ -690,7 +691,9 @@ class MachineIDResetter:
return False return False
def run(translator=None): def run(translator=None):
"""Convenient function for directly calling the reset function""" config = get_config(translator)
if not config:
return False
print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}") print(f"\n{Fore.CYAN}{'='*50}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{EMOJI['RESET']} {translator.get('reset.title')}{Style.RESET_ALL}") print(f"{Fore.CYAN}{EMOJI['RESET']} {translator.get('reset.title')}{Style.RESET_ALL}")
print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}") print(f"{Fore.CYAN}{'='*50}{Style.RESET_ALL}")

32
utils.py Normal file
View File

@ -0,0 +1,32 @@
import os
import sys
import platform
def get_user_documents_path():
"""Get user documents path"""
if platform.system() == "Windows":
return os.path.expanduser("~\\Documents")
else:
return os.path.expanduser("~/Documents")
def get_default_chrome_path():
"""Get default Chrome path"""
if sys.platform == "win32":
return r"C:\Program Files\Google\Chrome\Application\chrome.exe"
elif sys.platform == "darwin":
return "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
else:
return "/usr/bin/google-chrome"
def get_linux_cursor_path():
"""Get Linux Cursor path"""
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")
]
# 返回第一个存在的路径,如果都不存在则返回第一个路径
return next((path for path in possible_paths if os.path.exists(path)), possible_paths[0])