feat(s2a_service): Add pure API authorization mode without browser

- Add S2A_API_MODE configuration option to enable browser-less authorization
- Implement S2AApiAuthorizer class using curl_cffi for browser fingerprint simulation
- Add Sentinel PoW (Proof of Work) solver with FNV-1a hashing algorithm
- Implement OAuth flow via direct API calls instead of browser automation
- Add s2a_api_authorize() function to handle email/password authentication
- Support proxy configuration for API requests
- Add requirements token generation for API authentication
- Update browser_automation.py to check S2A_API_MODE and route to API or browser flow
- Update config.py to load S2A_API_MODE from configuration
- Add api_mode option to config.toml.example with documentation
- Improves performance and stability by eliminating browser overhead while maintaining compatibility with existing browser-based flow
This commit is contained in:
2026-02-02 09:26:57 +08:00
parent ae86ca42df
commit a7867ae406
5 changed files with 588 additions and 5 deletions

View File

@@ -48,6 +48,7 @@ from config import (
S2A_GROUP_NAMES,
S2A_GROUP_IDS,
S2A_ADMIN_KEY,
S2A_API_MODE,
BROWSER_RANDOM_FINGERPRINT,
batch_remove_teams_by_names,
)
@@ -55,7 +56,8 @@ from utils import load_team_tracker, get_all_incomplete_accounts, save_team_trac
from bot_notifier import BotNotifier, set_notifier, progress_finish
from s2a_service import (
s2a_get_dashboard_stats, format_dashboard_stats, s2a_get_keys_with_usage, format_keys_usage,
s2a_get_error_accounts, s2a_delete_account, s2a_batch_delete_error_accounts
s2a_get_error_accounts, s2a_delete_account, s2a_batch_delete_error_accounts,
s2a_api_authorize_single, s2a_batch_api_authorize
)
from email_service import gptmail_service, unified_create_email
from logger import log
@@ -159,6 +161,7 @@ class ProvisionerBot:
("include_owners", self.cmd_include_owners),
("reload", self.cmd_reload),
("s2a_config", self.cmd_s2a_config),
("api_auth", self.cmd_api_auth),
("clean", self.cmd_clean),
("clean_errors", self.cmd_clean_errors),
("clean_teams", self.cmd_clean_teams),
@@ -287,6 +290,7 @@ class ProvisionerBot:
BotCommand("keys_usage", "查看 API 密钥用量"),
BotCommand("stock", "查看账号库存"),
BotCommand("s2a_config", "配置 S2A 参数"),
BotCommand("api_auth", "API 模式授权账号"),
BotCommand("import", "导入账号到 team.json"),
BotCommand("verify", "验证未验证的账号"),
BotCommand("verify_all", "强制重新验证所有账号"),
@@ -355,6 +359,7 @@ class ProvisionerBot:
/keys_usage - 查看 API 密钥用量
/stock - 查看账号库存
/s2a_config - 配置 S2A 参数
/api_auth - API 模式授权 (无需浏览器)
/clean_errors - 清理错误状态账号
<b>🧹 清理管理:</b>
@@ -1072,6 +1077,92 @@ class ProvisionerBot:
except Exception as e:
await update.message.reply_text(f"❌ 修改配置失败: {e}")
@admin_only
async def cmd_api_auth(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""使用纯 API 模式授权账号到 S2A (无需浏览器)
用法:
/api_auth email password - 授权单个账号
/api_auth - 显示帮助信息
"""
if AUTH_PROVIDER != "s2a":
await update.message.reply_text(
"❌ 此命令仅在 S2A 模式下可用\n"
"当前授权服务: " + AUTH_PROVIDER
)
return
# 无参数时显示帮助
if not context.args:
api_mode_status = "✅ 已启用" if S2A_API_MODE else "❌ 未启用"
lines = [
"<b>🔐 S2A API 授权</b>",
"",
f"<b>API 模式:</b> {api_mode_status}",
"",
"<b>用法:</b>",
"<code>/api_auth email password</code>",
"",
"<b>示例:</b>",
"<code>/api_auth test@example.com MyPassword123</code>",
"",
"<b>说明:</b>",
"• 使用纯 API 方式完成 OAuth 授权",
"• 无需浏览器,更快更稳定",
"• 需要安装 curl_cffi: <code>pip install curl_cffi</code>",
"",
"<b>💡 提示:</b>",
"• 在 config.toml 中设置 <code>s2a.api_mode = true</code>",
"• 可让所有 S2A 授权自动使用 API 模式",
]
await update.message.reply_text("\n".join(lines), parse_mode="HTML")
return
# 解析参数
if len(context.args) < 2:
await update.message.reply_text(
"❌ 参数不足\n"
"用法: /api_auth <email> <password>"
)
return
email = context.args[0]
password = " ".join(context.args[1:]) # 密码可能包含空格
# 发送处理中消息
msg = await update.message.reply_text(
f"🔄 正在授权: <code>{email}</code>\n"
"请稍候...",
parse_mode="HTML"
)
try:
# 执行 API 授权
success, message = s2a_api_authorize_single(email, password)
if success:
await msg.edit_text(
f"✅ <b>授权成功</b>\n\n"
f"📧 邮箱: <code>{email}</code>\n"
f"📝 {message}",
parse_mode="HTML"
)
else:
await msg.edit_text(
f"❌ <b>授权失败</b>\n\n"
f"📧 邮箱: <code>{email}</code>\n"
f"📝 {message}",
parse_mode="HTML"
)
except Exception as e:
await msg.edit_text(
f"❌ <b>授权异常</b>\n\n"
f"📧 邮箱: <code>{email}</code>\n"
f"📝 错误: {str(e)}",
parse_mode="HTML"
)
@admin_only
async def cmd_run(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
"""启动处理指定数量的 Team - 交互式选择"""