重构机器人

This commit is contained in:
dela
2026-01-30 10:48:56 +08:00
parent 81577a3a59
commit 9b7ecb7b80
17 changed files with 1270 additions and 1 deletions

7
bot/handlers/__init__.py Normal file
View File

@@ -0,0 +1,7 @@
"""
Bot 命令处理器模块
"""
from bot.handlers import start, go, status, settings
__all__ = ["start", "go", "status", "settings"]

290
bot/handlers/go.py Normal file
View File

@@ -0,0 +1,290 @@
"""
/go 命令处理器 - 生成支付链接并发送 JSON 文件
"""
import asyncio
from telegram import Update
from telegram.ext import ContextTypes
from bot.middlewares.auth import require_auth
from bot.services.task_manager import TaskStatus
from bot.services.file_sender import send_results_as_json
from bot.handlers.settings import get_user_settings, DEFAULT_WORKERS
PLAN_MAPPING = {
"plus": "chatgptplusplan",
"pro": "chatgptproplan",
"chatgptplusplan": "chatgptplusplan",
"chatgptproplan": "chatgptproplan",
}
@require_auth
async def go_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""
处理 /go 命令 - 注册账号并生成支付链接
用法:
/go - 生成 1 个 Plus 支付链接
/go 5 - 生成 5 个 Plus 支付链接
/go 5 plus - 生成 5 个 Plus 支付链接
/go 3 pro - 生成 3 个 Pro 支付链接
"""
# 解析参数
num_accounts = 1
plan_input = "plus"
if context.args:
# 第一个参数:数量
try:
num_accounts = int(context.args[0])
if num_accounts < 1:
num_accounts = 1
elif num_accounts > 10:
await update.message.reply_text(
"⚠️ 单次最多处理 10 个账号,已自动调整为 10"
)
num_accounts = 10
except ValueError:
# 可能第一个参数是 plan
if context.args[0].lower() in PLAN_MAPPING:
plan_input = context.args[0].lower()
else:
await update.message.reply_text(
"❌ 无效参数\n\n"
"用法: `/go [数量] [plan]`\n"
"示例:\n"
"• `/go` - 1个 Plus 计划\n"
"• `/go 5` - 5个 Plus 计划\n"
"• `/go 3 pro` - 3个 Pro 计划",
parse_mode="Markdown",
)
return
# 第二个参数plan
if len(context.args) > 1:
plan_input = context.args[1].lower()
# 验证 plan
plan_name = PLAN_MAPPING.get(plan_input)
if not plan_name:
await update.message.reply_text(
f"❌ 无效的订阅计划: `{plan_input}`\n\n" f"可选值: `plus`, `pro`",
parse_mode="Markdown",
)
return
plan_display = "Plus" if "plus" in plan_name else "Pro"
# 获取配置和任务管理器
config = context.bot_data["config"]
task_manager = context.bot_data["task_manager"]
user_id = update.effective_user.id
# 获取用户设置的并发数
user_settings = get_user_settings(context, user_id)
workers = user_settings.get("workers", DEFAULT_WORKERS)
# 创建任务
task_id = task_manager.create_task(
user_id=user_id,
task_type="go",
total=num_accounts,
description=f"{num_accounts} x {plan_display} 支付链接",
)
await update.message.reply_text(
f"🚀 **开始生成支付链接**\n\n"
f"📦 计划: {plan_display}\n"
f"📊 数量: {num_accounts}\n"
f"👷 并发: {workers}\n"
f"📋 任务 ID: `{task_id}`\n\n"
f"使用 `/status {task_id}` 查看进度",
parse_mode="Markdown",
)
# 在后台执行工作流
asyncio.create_task(
_run_go_workflow(
config, task_manager, task_id, num_accounts, plan_name, workers, update, context
)
)
async def _run_go_workflow(
config,
task_manager,
task_id: str,
num_accounts: int,
plan_name: str,
workers: int,
update: Update,
context: ContextTypes.DEFAULT_TYPE,
):
"""
后台执行工作流:并发注册 + 登录 + 获取支付链接
"""
plan_display = "Plus" if "plus" in plan_name else "Pro"
semaphore = asyncio.Semaphore(workers)
async def process_one(index: int):
async with semaphore:
account_num = index + 1
try:
task_manager.update_progress(
task_id,
current_item=f"账号 {account_num}/{num_accounts}",
)
result = await _register_with_checkout(config, account_num, plan_name)
return result
except Exception as e:
return {
"status": "failed",
"failed_stage": "exception",
"error": str(e),
}
# 并发执行所有任务
tasks = [process_one(i) for i in range(num_accounts)]
results = await asyncio.gather(*tasks)
# 更新进度
success_count = sum(1 for r in results if r.get("status") == "success")
failed_count = len(results) - success_count
task_manager.update_progress(task_id, completed=num_accounts)
# 完成任务
task_manager.complete_task(
task_id,
status=TaskStatus.COMPLETED if failed_count == 0 else TaskStatus.PARTIAL,
result={
"success": success_count,
"failed": failed_count,
"total": num_accounts,
"plan": plan_display,
},
)
# 发送 JSON 文件结果
await send_results_as_json(
update=update,
context=context,
task_id=task_id,
plan=plan_display,
results=results,
)
async def _register_with_checkout(config, task_id: int, plan_name: str):
"""
执行完整的注册 + 登录 + checkout 流程
返回包含所有信息的结果字典
"""
from core.session import OAISession
from core.flow import RegisterFlow
from core.login_flow import LoginFlow
from core.checkout import CheckoutFlow
from utils.logger import logger
import re
def _mask_proxy(proxy: str) -> str:
return re.sub(r"://([^:]+):([^@]+)@", r"://***:***@", proxy)
# 选择代理
proxy = config.proxy.get_next_proxy()
if proxy:
logger.info(f"[Go {task_id}] Using proxy: {_mask_proxy(proxy)}")
session = None
try:
session = OAISession(proxy=proxy, impersonate=config.tls_impersonate)
# Step 1: 注册
logger.info(f"[Go {task_id}] Step 1: Registering...")
flow = RegisterFlow(session, config)
reg_result = await flow.run()
if reg_result.get("status") != "success":
return {
"status": "failed",
"failed_stage": "register",
"error": reg_result.get("error", "Registration failed"),
}
email = reg_result.get("email")
password = reg_result.get("password")
logger.info(f"[Go {task_id}] Registered: {email}")
# Step 2: 登录获取 Token
logger.info(f"[Go {task_id}] Step 2: Logging in...")
login_flow = LoginFlow(session, email, password)
login_result = await login_flow.run()
if login_result.get("status") != "success":
return {
"status": "partial",
"failed_stage": "login",
"email": email,
"password": password,
"error": login_result.get("error", "Login failed"),
}
session.access_token = login_result.get("access_token")
session.session_token = login_result.get("session_token")
logger.info(f"[Go {task_id}] Logged in, token obtained")
# Step 3: 获取支付链接
logger.info(f"[Go {task_id}] Step 3: Creating checkout session for {plan_name}...")
checkout = CheckoutFlow(session, plan_name=plan_name)
checkout_result = await checkout.create_checkout_session()
result = {
"status": "success",
"email": email,
"password": password,
"access_token": login_result.get("access_token"),
"plan_name": plan_name,
}
if checkout_result.get("status") == "success":
result["checkout_session_id"] = checkout_result.get("checkout_session_id")
result["checkout_url"] = checkout_result.get("url", "")
result["client_secret"] = checkout_result.get("client_secret")
logger.info(f"[Go {task_id}] Checkout session created")
# 保存结果
from main import save_account, save_checkout_result
await save_account(result, config.accounts_output_file)
checkout_result["email"] = email
await save_checkout_result(checkout_result)
else:
# Checkout 失败但账号创建成功
result["status"] = "partial"
result["checkout_error"] = checkout_result.get("error")
logger.warning(f"[Go {task_id}] Checkout failed: {checkout_result.get('error')}")
from main import save_account
await save_account(result, config.accounts_output_file)
return result
except Exception as e:
logger.exception(f"[Go {task_id}] Unexpected error")
return {
"status": "failed",
"failed_stage": "exception",
"error": str(e),
}
finally:
if session:
try:
session.close()
except Exception as e:
logger.warning(f"[Go {task_id}] Error closing session: {e}")

96
bot/handlers/settings.py Normal file
View File

@@ -0,0 +1,96 @@
"""
/set 命令处理器 - 用户设置
"""
from telegram import Update
from telegram.ext import ContextTypes
from bot.middlewares.auth import require_auth
DEFAULT_WORKERS = 2
MIN_WORKERS = 1
MAX_WORKERS = 5
def get_user_settings(context: ContextTypes.DEFAULT_TYPE, user_id: int) -> dict:
"""获取用户设置"""
if "user_settings" not in context.bot_data:
context.bot_data["user_settings"] = {}
if user_id not in context.bot_data["user_settings"]:
context.bot_data["user_settings"][user_id] = {
"workers": DEFAULT_WORKERS,
}
return context.bot_data["user_settings"][user_id]
def set_user_setting(
context: ContextTypes.DEFAULT_TYPE, user_id: int, key: str, value: any
) -> None:
"""设置用户配置"""
settings = get_user_settings(context, user_id)
settings[key] = value
@require_auth
async def set_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""
处理 /set 命令 - 用户设置
用法:
/set - 查看当前设置
/set workers <数量> - 设置并发数 (1-5)
"""
user_id = update.effective_user.id
settings = get_user_settings(context, user_id)
# 无参数:显示当前设置
if not context.args:
await update.message.reply_text(
f"⚙️ **当前设置**\n\n"
f"👷 并发数: {settings['workers']}\n\n"
f"使用 `/set workers <数量>` 修改并发数 ({MIN_WORKERS}-{MAX_WORKERS})",
parse_mode="Markdown",
)
return
# 解析设置项
setting_name = context.args[0].lower()
if setting_name == "workers":
if len(context.args) < 2:
await update.message.reply_text(
f"❌ 请指定并发数\n\n"
f"用法: `/set workers <数量>`\n"
f"范围: {MIN_WORKERS}-{MAX_WORKERS}",
parse_mode="Markdown",
)
return
try:
workers = int(context.args[1])
if workers < MIN_WORKERS:
workers = MIN_WORKERS
elif workers > MAX_WORKERS:
workers = MAX_WORKERS
set_user_setting(context, user_id, "workers", workers)
await update.message.reply_text(
f"✅ 并发数已设置为 **{workers}**",
parse_mode="Markdown",
)
except ValueError:
await update.message.reply_text(
f"❌ 无效的数值: `{context.args[1]}`",
parse_mode="Markdown",
)
else:
await update.message.reply_text(
f"❌ 未知设置项: `{setting_name}`\n\n"
f"可用设置:\n"
f"• `workers` - 并发数 ({MIN_WORKERS}-{MAX_WORKERS})",
parse_mode="Markdown",
)

57
bot/handlers/start.py Normal file
View File

@@ -0,0 +1,57 @@
"""
/start 和 /help 命令处理器
"""
from telegram import Update
from telegram.ext import ContextTypes
from bot.middlewares.auth import require_auth
@require_auth
async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""
处理 /start 命令 - 欢迎信息
"""
user = update.effective_user
welcome_text = f"""
👋 你好, {user.first_name}!
🔗 **支付链接生成器**
📋 命令:
• `/go [数量] [plan]` - 生成支付链接
• `/status [task_id]` - 查看任务状态
• `/set workers <数量>` - 设置并发数
💡 使用 /help 查看详细帮助
"""
await update.message.reply_text(welcome_text, parse_mode="Markdown")
@require_auth
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""
处理 /help 命令 - 详细帮助信息
"""
help_text = """
📖 **命令说明**
**生成支付链接**
`/go` - 生成 1 个 Plus 支付链接
`/go 5` - 生成 5 个 Plus 支付链接
`/go 3 pro` - 生成 3 个 Pro 支付链接
可选 plan: `plus` (默认) 或 `pro`
**查看任务状态**
`/status` - 查看所有任务
`/status <task_id>` - 查看指定任务
**设置**
`/set` - 查看当前设置
`/set workers 3` - 设置并发数 (1-5)
📎 结果将以 JSON 文件形式发送
"""
await update.message.reply_text(help_text, parse_mode="Markdown")

112
bot/handlers/status.py Normal file
View File

@@ -0,0 +1,112 @@
"""
/status 命令处理器 - 查看任务状态
"""
from telegram import Update
from telegram.ext import ContextTypes
from bot.middlewares.auth import require_auth
from bot.services.task_manager import TaskStatus
STATUS_EMOJI = {
TaskStatus.PENDING: "",
TaskStatus.RUNNING: "🔄",
TaskStatus.COMPLETED: "",
TaskStatus.FAILED: "",
TaskStatus.PARTIAL: "⚠️",
}
@require_auth
async def status_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""
处理 /status 命令 - 查看任务状态
用法:
/status - 查看所有进行中的任务
/status <task_id> - 查看指定任务详情
"""
task_manager = context.bot_data["task_manager"]
user_id = update.effective_user.id
# 检查是否指定了任务 ID
if context.args:
task_id = context.args[0]
task = task_manager.get_task(task_id)
if not task:
await update.message.reply_text(f"❌ 未找到任务: `{task_id}`", parse_mode="Markdown")
return
# 检查是否是自己的任务(或管理员)
admin_users = context.bot_data.get("admin_users", set())
if task.user_id != user_id and user_id not in admin_users:
await update.message.reply_text("❌ 你没有权限查看此任务")
return
# 显示任务详情
emoji = STATUS_EMOJI.get(task.status, "")
progress_pct = (task.completed / task.total * 100) if task.total > 0 else 0
text = (
f"{emoji} **任务详情**\n\n"
f"🆔 ID: `{task.task_id}`\n"
f"📋 类型: {task.task_type}\n"
f"📝 描述: {task.description}\n"
f"📊 状态: {task.status.value}\n"
f"📈 进度: {task.completed}/{task.total} ({progress_pct:.0f}%)\n"
)
if task.current_item:
text += f"🔄 当前: {task.current_item}\n"
if task.result:
text += f"\n📦 结果:\n```\n{task.result}\n```"
await update.message.reply_text(text, parse_mode="Markdown")
return
# 获取用户的所有任务
admin_users = context.bot_data.get("admin_users", set())
is_admin = user_id in admin_users
if is_admin:
# 管理员可以看所有任务
tasks = task_manager.get_all_tasks()
else:
# 普通用户只能看自己的
tasks = task_manager.get_user_tasks(user_id)
if not tasks:
await update.message.reply_text(
"📭 没有找到任务记录\n\n"
"使用 `/register` 开始注册账号",
parse_mode="Markdown"
)
return
# 构建任务列表
text = "📋 **任务列表**\n\n"
# 先显示进行中的任务
running_tasks = [t for t in tasks if t.status in [TaskStatus.PENDING, TaskStatus.RUNNING]]
completed_tasks = [t for t in tasks if t.status not in [TaskStatus.PENDING, TaskStatus.RUNNING]]
if running_tasks:
text += "**进行中:**\n"
for task in running_tasks[:5]:
emoji = STATUS_EMOJI.get(task.status, "")
progress_pct = (task.completed / task.total * 100) if task.total > 0 else 0
text += f"{emoji} `{task.task_id}` - {task.description} ({progress_pct:.0f}%)\n"
text += "\n"
if completed_tasks:
text += "**已完成:**\n"
for task in completed_tasks[:5]:
emoji = STATUS_EMOJI.get(task.status, "")
text += f"{emoji} `{task.task_id}` - {task.description}\n"
text += f"\n💡 使用 `/status <task_id>` 查看详情"
await update.message.reply_text(text, parse_mode="Markdown")