diff --git a/START_README.md b/START_README.md new file mode 100644 index 0000000..ae9b443 --- /dev/null +++ b/START_README.md @@ -0,0 +1,118 @@ +# Cursor自动化服务统一启动脚本 + +这个统一启动脚本(`start.py`)用于集中管理Cursor注册的各项功能,包括注册状态检查、账号上传和自动化服务。 + +## 功能特点 + +1. **集中管理** + - 通过数据库标记控制自动服务的启停 + - 提供统一的命令行接口管理各项功能 + +2. **灵活操作** + - 可以单独启动注册进程 + - 可以执行单次账号上传 + - 可以管理自动化服务的状态 + +3. **简化部署** + - 无需手动启动多个脚本 + - 支持作为服务自动启动 + +## 使用方法 + +### 基本用法 + +不带参数启动时,脚本会检查数据库中的启动标记,并据此启动或停止自动服务: + +```bash +python start.py +``` + +### 启用自动服务 + +设置启动标记并启动自动服务: + +```bash +python start.py --enable +``` + +### 禁用自动服务 + +清除启动标记并停止自动服务: + +```bash +python start.py --disable +``` + +### 查看服务状态 + +查看当前自动服务的状态: + +```bash +python start.py --status +``` + +### 手动上传账号 + +执行一次账号上传操作,不影响自动服务: + +```bash +python start.py --upload +``` + +### 仅运行注册进程 + +直接启动注册进程,不启动完整的自动服务: + +```bash +python start.py --register +``` + +## 工作原理 + +1. 脚本使用数据库中的`system_settings`表存储自动服务的启用状态 +2. 当状态为启用时,脚本会自动启动`auto_cursor_service.py` +3. 自动服务会负责检查API状态、获取邮箱和上传账号等操作 +4. 如果只需单独功能,可以使用相应的命令行参数 + +## 设置为系统服务 + +### Linux (systemd) + +创建服务文件`/etc/systemd/system/cursor-service.service`: + +``` +[Unit] +Description=Cursor Auto Registration Service +After=network.target mysql.service redis.service + +[Service] +Type=simple +User=your_user +WorkingDirectory=/path/to/cursor/app +ExecStart=/usr/bin/python3 /path/to/cursor/app/start.py +Restart=on-failure +RestartSec=10 + +[Install] +WantedBy=multi-user.target +``` + +启动服务: + +```bash +sudo systemctl daemon-reload +sudo systemctl start cursor-service +sudo systemctl enable cursor-service +``` + +### Windows (开机自启) + +创建批处理文件`start_cursor.bat`: + +```bat +@echo off +cd /d "C:\path\to\cursor\app" +python start.py +``` + +将此批处理文件添加到启动文件夹。 \ No newline at end of file diff --git a/main.py b/main.py index 61aafe4..e628e78 100644 --- a/main.py +++ b/main.py @@ -144,17 +144,14 @@ async def main(): total_registered = 0 while True: - # 检查是否还有可用的邮箱账号 - available_accounts = await register.email_manager.batch_get_accounts(1) - if not available_accounts: - register.logger.info("没有更多可用的邮箱账号,注册完成") + # 直接检查数据库中是否有可用的邮箱账号 + pending_count = await register.email_manager.count_pending_accounts() + if pending_count <= 0: + register.logger.info("没有可用的邮箱账号,注册完成") break - - # 释放检查用的账号 - await register.email_manager.update_account_status(available_accounts[0].id, 'pending') # 执行批量注册 - register.logger.info(f"开始新一轮批量注册,批次大小: {batch_size}") + register.logger.info(f"发现 {pending_count} 个可用邮箱,开始新一轮批量注册,批次大小: {batch_size}") results = await register.batch_register(batch_size) # 统计结果 @@ -164,9 +161,12 @@ async def main(): register.logger.info(f"当前总进度: 已注册 {total_registered} 个账号") # 如果本批次注册失败率过高,暂停一段时间 - if successful < batch_size * 0.5: # 成功率低于50% + if successful < batch_size * 0.5 and successful > 0: # 成功率低于50%但不为零 register.logger.warning("本批次成功率过低,暂停60秒后继续") await asyncio.sleep(60) + elif successful == 0 and batch_size > 0: # 完全失败 + register.logger.error("本批次完全失败,可能存在系统问题,暂停120秒后继续") + await asyncio.sleep(120) else: # 正常等待一个较短的时间再继续下一批 await asyncio.sleep(5) diff --git a/services/email_manager.py b/services/email_manager.py index c369d4c..8cb3169 100644 --- a/services/email_manager.py +++ b/services/email_manager.py @@ -44,6 +44,10 @@ class EmailManager: SELECT id, email, password, client_id, refresh_token FROM email_accounts WHERE in_use = 0 AND sold = 0 AND status = 'pending' + AND email NOT IN ( + SELECT email FROM email_accounts + WHERE status = 'success' OR status = 'unavailable' + ) LIMIT %s """ accounts = await self.db.fetch_all(select_query, (num,)) @@ -55,7 +59,7 @@ class EmailManager: # 2. 提取账号ID列表 account_ids = [account['id'] for account in accounts] - # 3. 更新这些账号的状态 + # 3. 更新这些账号的状态,设置注册开始时间戳,避免其他进程获取 if account_ids: placeholders = ', '.join(['%s' for _ in account_ids]) update_query = f""" @@ -64,6 +68,9 @@ class EmailManager: WHERE id IN ({placeholders}) """ await self.db.execute(update_query, tuple(account_ids)) + + # 记录被锁定的账号信息 + logger.info(f"已锁定 {len(account_ids)} 个账号用于注册: {account_ids}") # 4. 返回账号数据 logger.debug(f"实际获取到 {len(accounts)} 个账号") @@ -116,6 +123,22 @@ class EmailManager: ''' await self.db.execute(query, (account_id,)) + async def count_pending_accounts(self) -> int: + """统计可用的pending状态账号数量""" + query = """ + SELECT COUNT(*) + FROM email_accounts + WHERE status = 'pending' AND in_use = 0 AND sold = 0 + AND email NOT IN ( + SELECT email FROM email_accounts + WHERE status = 'success' OR status = 'unavailable' + ) + """ + result = await self.db.fetch_one(query) + if result: + return result.get("COUNT(*)", 0) + return 0 + async def _get_access_token(self, client_id: str, refresh_token: str) -> str: """获取微软 access token""" logger.debug(f"开始获取 access token - client_id: {client_id}") diff --git a/start.py b/start.py new file mode 100644 index 0000000..14ad90c --- /dev/null +++ b/start.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python3 +""" +Cursor自动化服务统一启动脚本 +- 检查数据库标记 +- 启动自动化服务或独立功能 +- 集中管理各项服务功能 +""" +import asyncio +import sys +import os +import argparse +import subprocess +import time +from typing import Optional, List + +from loguru import logger + +# Windows平台特殊处理,强制使用SelectorEventLoop +if sys.platform.startswith("win"): + asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) + +from core.config import Config +from core.database import DatabaseManager + + +# 配置日志 +logger.remove() +logger.add(sys.stderr, level="INFO") +logger.add( + "start.log", + rotation="10 MB", + retention="10 days", + level="DEBUG", + compression="zip" +) + + +class CursorServiceManager: + def __init__(self): + self.config = Config.from_yaml() + self.db_manager = DatabaseManager(self.config) + self.service_process = None + self.upload_process = None + + async def initialize(self): + """初始化数据库连接""" + logger.info("初始化数据库连接") + await self.db_manager.initialize() + + async def cleanup(self): + """清理资源""" + logger.info("清理资源") + if self.service_process and self.service_process.poll() is None: + logger.info("终止自动化服务进程") + try: + if sys.platform.startswith("win"): + subprocess.run(["taskkill", "/F", "/T", "/PID", str(self.service_process.pid)]) + else: + self.service_process.terminate() + self.service_process.wait(timeout=5) + except Exception as e: + logger.error(f"终止进程时出错: {e}") + + if self.upload_process and self.upload_process.poll() is None: + logger.info("终止上传进程") + try: + if sys.platform.startswith("win"): + subprocess.run(["taskkill", "/F", "/T", "/PID", str(self.upload_process.pid)]) + else: + self.upload_process.terminate() + self.upload_process.wait(timeout=5) + except Exception as e: + logger.error(f"终止进程时出错: {e}") + + await self.db_manager.cleanup() + + async def check_service_flag(self) -> bool: + """检查数据库中是否存在启动标记""" + try: + # 创建系统设置表(如果不存在) + await self.db_manager.execute(""" + CREATE TABLE IF NOT EXISTS system_settings ( + id INT AUTO_INCREMENT PRIMARY KEY, + key_name VARCHAR(50) UNIQUE NOT NULL, + value TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP + ) + """) + + # 检查是否存在自动服务启动标记 + result = await self.db_manager.fetch_one( + "SELECT value FROM system_settings WHERE key_name = 'auto_service_enabled'" + ) + + if result: + return result['value'] == '1' + else: + # 如果不存在记录,创建默认记录(不启用) + await self.db_manager.execute( + "INSERT INTO system_settings (key_name, value) VALUES ('auto_service_enabled', '0')" + ) + return False + + except Exception as e: + logger.error(f"检查服务标记时出错: {e}") + return False + + async def set_service_flag(self, enabled: bool): + """设置服务启动标记""" + try: + value = '1' if enabled else '0' + await self.db_manager.execute( + "INSERT INTO system_settings (key_name, value) VALUES ('auto_service_enabled', %s) " + "ON DUPLICATE KEY UPDATE value = %s", + (value, value) + ) + logger.success(f"自动服务标记已{'启用' if enabled else '禁用'}") + except Exception as e: + logger.error(f"设置服务标记时出错: {e}") + + def start_auto_service(self): + """启动自动化服务""" + if self.service_process and self.service_process.poll() is None: + logger.info("自动化服务已在运行中") + return + + logger.info("启动自动化服务") + try: + if sys.platform.startswith("win"): + self.service_process = subprocess.Popen( + ["python", "auto_cursor_service.py"], + creationflags=subprocess.CREATE_NEW_PROCESS_GROUP + ) + else: + self.service_process = subprocess.Popen( + ["python3", "auto_cursor_service.py"] + ) + + logger.info(f"自动化服务已启动,PID: {self.service_process.pid}") + except Exception as e: + logger.error(f"启动自动化服务时出错: {e}") + self.service_process = None + + def stop_auto_service(self): + """停止自动化服务""" + if not self.service_process or self.service_process.poll() is not None: + logger.info("自动化服务未在运行") + self.service_process = None + return + + logger.info("停止自动化服务") + try: + if sys.platform.startswith("win"): + subprocess.run(["taskkill", "/F", "/T", "/PID", str(self.service_process.pid)]) + else: + self.service_process.terminate() + self.service_process.wait(timeout=5) + + logger.info("自动化服务已停止") + except Exception as e: + logger.error(f"停止自动化服务时出错: {e}") + + self.service_process = None + + def start_upload(self): + """启动账号上传""" + logger.info("开始上传账号") + try: + if sys.platform.startswith("win"): + self.upload_process = subprocess.Popen( + ["python", "upload_account.py"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + creationflags=subprocess.CREATE_NO_WINDOW + ) + else: + self.upload_process = subprocess.Popen( + ["python3", "upload_account.py"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + + # 等待进程完成 + stdout, stderr = self.upload_process.communicate() + + if self.upload_process.returncode != 0: + logger.error(f"上传账号进程异常终止,退出码: {self.upload_process.returncode}") + if stderr: + logger.error(f"错误输出: {stderr.decode('utf-8', errors='ignore')}") + else: + logger.info("账号上传完成") + if stdout: + # 只记录最后几行输出 + output_lines = stdout.decode('utf-8', errors='ignore').strip().split('\n') + for line in output_lines[-5:]: + logger.info(f"Upload: {line}") + except Exception as e: + logger.error(f"执行账号上传脚本时出错: {e}") + + self.upload_process = None + + def start_register(self): + """启动注册进程""" + logger.info("启动注册进程") + try: + # 使用subprocess启动main.py + if sys.platform.startswith("win"): + register_process = subprocess.Popen( + ["python", "main.py"], + creationflags=subprocess.CREATE_NEW_PROCESS_GROUP + ) + else: + register_process = subprocess.Popen( + ["python3", "main.py"] + ) + + logger.info(f"注册进程已启动,PID: {register_process.pid}") + return register_process + except Exception as e: + logger.error(f"启动注册进程时出错: {e}") + return None + + async def run_service_check(self): + """检查并启动/停止自动服务""" + enabled = await self.check_service_flag() + + if enabled: + if not self.service_process or self.service_process.poll() is not None: + logger.info("自动服务标记已启用,但服务未运行,现在启动") + self.start_auto_service() + else: + if self.service_process and self.service_process.poll() is None: + logger.info("自动服务标记已禁用,但服务仍在运行,现在停止") + self.stop_auto_service() + + return enabled + + +async def main(): + """主函数""" + parser = argparse.ArgumentParser(description="Cursor自动化服务管理工具") + group = parser.add_mutually_exclusive_group() + group.add_argument("--enable", action="store_true", help="启用自动服务") + group.add_argument("--disable", action="store_true", help="禁用自动服务") + group.add_argument("--status", action="store_true", help="查看自动服务状态") + group.add_argument("--upload", action="store_true", help="手动上传账号") + group.add_argument("--register", action="store_true", help="仅运行注册进程") + + args = parser.parse_args() + + manager = CursorServiceManager() + await manager.initialize() + + try: + if args.enable: + await manager.set_service_flag(True) + manager.start_auto_service() + + elif args.disable: + await manager.set_service_flag(False) + manager.stop_auto_service() + + elif args.status: + enabled = await manager.check_service_flag() + print(f"自动服务状态: {'已启用' if enabled else '已禁用'}") + + # 检查进程是否在运行 + if manager.service_process and manager.service_process.poll() is None: + print(f"自动服务进程: 运行中 (PID: {manager.service_process.pid})") + else: + print("自动服务进程: 未运行") + + elif args.upload: + manager.start_upload() + + elif args.register: + register_process = manager.start_register() + if register_process: + print(f"注册进程已启动,PID: {register_process.pid}") + print("按Ctrl+C结束...") + try: + # 等待进程结束 + register_process.wait() + except KeyboardInterrupt: + print("正在终止注册进程...") + if sys.platform.startswith("win"): + subprocess.run(["taskkill", "/F", "/T", "/PID", str(register_process.pid)]) + else: + register_process.terminate() + + else: + # 默认行为:检查并根据标记启动/停止服务 + enabled = await manager.run_service_check() + print(f"自动服务状态: {'已启用' if enabled else '已禁用'}") + + if enabled: + print("自动服务已启动,按Ctrl+C停止...") + try: + # 保持进程运行,等待用户中断 + while True: + await asyncio.sleep(1) + except KeyboardInterrupt: + print("正在停止...") + + finally: + await manager.cleanup() + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file