This commit is contained in:
2026-01-18 06:04:06 +08:00
parent f919e71de1
commit 94145f7dd0
2 changed files with 161 additions and 123 deletions

234
run.py
View File

@@ -364,139 +364,139 @@ def process_accounts(accounts: list, team_name: str, team_index: int = 0,
if is_team_owner_otp: if is_team_owner_otp:
# 旧格式 Team Owner: 使用 OTP 登录授权 # 旧格式 Team Owner: 使用 OTP 登录授权
log.info("Team Owner 账号 (旧格式),使用一次性验证码登录...", icon="auth") log.info("Team Owner 账号 (旧格式),使用一次性验证码登录...", icon="auth")
progress_update(step="OTP Login...") progress_update(step="OTP Login...")
auth_success, codex_data = login_and_authorize_with_otp(email) auth_success, codex_data = login_and_authorize_with_otp(email)
register_success = auth_success
elif need_crs_only:
# 已授权但未入库: 跳过授权,直接尝试入库
log.info(f"已授权账号 (状态: {account_status}),跳过授权,直接入库...", icon="auth")
progress_update(step="Adding to CRS...")
register_success = True
codex_data = None # CPA/S2A 模式不需要 codex_data
# CRS 模式下,由于没有 codex_data无法入库需要重新授权
if AUTH_PROVIDER not in ("cpa", "s2a"):
log.warning("CRS 模式下已授权账号缺少 codex_data需要重新授权")
auth_success, codex_data = authorize_only(email, password)
register_success = auth_success register_success = auth_success
elif need_auth_only: elif need_crs_only:
# 已注册账号 (包括新格式 Owner): 使用密码登录授权 # 已授权但未入库: 跳过授权,直接尝试入库
log.info(f"注册账号 (状态: {account_status}, 角色: {account_role}),使用密码登录授权...", icon="auth") log.info(f"授权账号 (状态: {account_status}),跳过授权,直接入库...", icon="auth")
progress_update(step="Authorizing...") progress_update(step="Adding to CRS...")
auth_success, codex_data = authorize_only(email, password) register_success = True
register_success = True codex_data = None # CPA/S2A 模式不需要 codex_data
else: # CRS 模式下,由于没有 codex_data无法入库需要重新授权
# 新账号: 注册 + Codex 授权 if AUTH_PROVIDER not in ("cpa", "s2a"):
progress_update(step="Registering...") log.warning("CRS 模式下已授权账号缺少 codex_data需要重新授权")
register_success, codex_data = register_and_authorize(email, password) auth_success, codex_data = authorize_only(email, password)
register_success = auth_success
# 检查是否是域名黑名单错误 elif need_auth_only:
if register_success == "domain_blacklisted": # 已注册账号 (包括新格式 Owner): 使用密码登录授权
domain = get_domain_from_email(email) log.info(f"已注册账号 (状态: {account_status}, 角色: {account_role}),使用密码登录授权...", icon="auth")
log.error(f"域名 {domain} 不被支持,加入黑名单") progress_update(step="Authorizing...")
add_domain_to_blacklist(domain) auth_success, codex_data = authorize_only(email, password)
register_success = True
# 从 tracker 中移除
remove_account_from_tracker(_tracker, team_name, email)
save_team_tracker(_tracker)
# 尝试创建新邮箱替代
log.info("尝试创建新邮箱替代...")
new_email, new_password = unified_create_email()
if new_email and not is_email_blacklisted(new_email):
# 邀请新邮箱
if invite_single_to_team(new_email, _get_team_by_name(team_name)):
add_account_with_password(_tracker, team_name, new_email, new_password, "invited")
save_team_tracker(_tracker)
log.success(f"已创建新邮箱: {new_email},将在下次运行时处理")
else:
log.error("新邮箱邀请失败")
else:
log.error("无法创建有效的新邮箱")
continue # 跳过当前账号,继续下一个
if register_success and register_success != "domain_blacklisted":
update_account_status(_tracker, team_name, email, "registered")
save_team_tracker(_tracker)
# CPA 模式: codex_data 为 None授权成功后直接标记完成
# CRS 模式: 需要 codex_data手动添加到 CRS
if AUTH_PROVIDER == "s2a":
# S2A 模式: 授权成功后验证账号是否入库
from s2a_service import s2a_verify_account_in_pool
update_account_status(_tracker, team_name, email, "authorized")
save_team_tracker(_tracker)
# 验证账号是否成功入库
log.step("正在验证 S2A 账号入库状态...")
progress_update(step="验证入库...")
verified, account_data = s2a_verify_account_in_pool(email)
if verified:
account_id = account_data.get("id", "")
account_name = account_data.get("name", "")
result["status"] = "success"
result["crs_id"] = f"S2A-{account_id}"
update_account_status(_tracker, team_name, email, "completed")
save_team_tracker(_tracker)
log.success(f"✅ S2A 账号入库成功 (ID: {account_id}, 名称: {account_name})")
else:
log.warning("⚠️ S2A 授权成功但入库验证失败")
result["status"] = "partial"
update_account_status(_tracker, team_name, email, "partial")
save_team_tracker(_tracker)
elif AUTH_PROVIDER == "cpa":
# CPA 模式: 授权成功即完成 (后台自动处理账号)
update_account_status(_tracker, team_name, email, "authorized")
save_team_tracker(_tracker)
result["status"] = "success"
result["crs_id"] = "CPA-AUTO"
update_account_status(_tracker, team_name, email, "completed")
save_team_tracker(_tracker)
log.success(f"✅ CPA 账号处理完成: {email}")
else: else:
# CRS 模式: 原有逻辑 # 新账号: 注册 + Codex 授权
if codex_data: progress_update(step="Registering...")
register_success, codex_data = register_and_authorize(email, password)
# 检查是否是域名黑名单错误
if register_success == "domain_blacklisted":
domain = get_domain_from_email(email)
log.error(f"域名 {domain} 不被支持,加入黑名单")
add_domain_to_blacklist(domain)
# 从 tracker 中移除
remove_account_from_tracker(_tracker, team_name, email)
save_team_tracker(_tracker)
# 尝试创建新邮箱替代
log.info("尝试创建新邮箱替代...")
new_email, new_password = unified_create_email()
if new_email and not is_email_blacklisted(new_email):
# 邀请新邮箱
if invite_single_to_team(new_email, _get_team_by_name(team_name)):
add_account_with_password(_tracker, team_name, new_email, new_password, "invited")
save_team_tracker(_tracker)
log.success(f"已创建新邮箱: {new_email},将在下次运行时处理")
else:
log.error("新邮箱邀请失败")
else:
log.error("无法创建有效的新邮箱")
continue # 跳过当前账号,继续下一个
if register_success and register_success != "domain_blacklisted":
update_account_status(_tracker, team_name, email, "registered")
save_team_tracker(_tracker)
# CPA 模式: codex_data 为 None授权成功后直接标记完成
# CRS 模式: 需要 codex_data手动添加到 CRS
if AUTH_PROVIDER == "s2a":
# S2A 模式: 授权成功后验证账号是否入库
from s2a_service import s2a_verify_account_in_pool
update_account_status(_tracker, team_name, email, "authorized") update_account_status(_tracker, team_name, email, "authorized")
save_team_tracker(_tracker) save_team_tracker(_tracker)
# 添加到 CRS # 验证账号是否成功入库
log.step("添加到 CRS...") log.step("正在验证 S2A 账号入库状态...")
crs_result = crs_add_account(email, codex_data) progress_update(step="验证入库...")
verified, account_data = s2a_verify_account_in_pool(email)
if crs_result: if verified:
crs_id = crs_result.get("id", "") account_id = account_data.get("id", "")
account_name = account_data.get("name", "")
result["status"] = "success" result["status"] = "success"
result["crs_id"] = crs_id result["crs_id"] = f"S2A-{account_id}"
update_account_status(_tracker, team_name, email, "completed") update_account_status(_tracker, team_name, email, "completed")
save_team_tracker(_tracker) save_team_tracker(_tracker)
log.success(f"账号处理完成: {email}") log.success(f"✅ S2A 账号入库成功 (ID: {account_id}, 名称: {account_name})")
else: else:
log.warning("CRS 入库失败,但注册和授权成功") log.warning("⚠️ S2A 授权成功但入库验证失败")
result["status"] = "partial" result["status"] = "partial"
update_account_status(_tracker, team_name, email, "partial") update_account_status(_tracker, team_name, email, "partial")
save_team_tracker(_tracker) save_team_tracker(_tracker)
else:
log.warning("Codex 授权失败") elif AUTH_PROVIDER == "cpa":
result["status"] = "auth_failed" # CPA 模式: 授权成功即完成 (后台自动处理账号)
update_account_status(_tracker, team_name, email, "auth_failed") update_account_status(_tracker, team_name, email, "authorized")
save_team_tracker(_tracker) save_team_tracker(_tracker)
elif register_success != "domain_blacklisted":
if is_team_owner_otp: result["status"] = "success"
log.error(f"OTP 登录授权失败: {email}") result["crs_id"] = "CPA-AUTO"
else:
log.error(f"注册/授权失败: {email}") update_account_status(_tracker, team_name, email, "completed")
update_account_status(_tracker, team_name, email, "register_failed") save_team_tracker(_tracker)
save_team_tracker(_tracker)
log.success(f"✅ CPA 账号处理完成: {email}")
else:
# CRS 模式: 原有逻辑
if codex_data:
update_account_status(_tracker, team_name, email, "authorized")
save_team_tracker(_tracker)
# 添加到 CRS
log.step("添加到 CRS...")
crs_result = crs_add_account(email, codex_data)
if crs_result:
crs_id = crs_result.get("id", "")
result["status"] = "success"
result["crs_id"] = crs_id
update_account_status(_tracker, team_name, email, "completed")
save_team_tracker(_tracker)
log.success(f"账号处理完成: {email}")
else:
log.warning("CRS 入库失败,但注册和授权成功")
result["status"] = "partial"
update_account_status(_tracker, team_name, email, "partial")
save_team_tracker(_tracker)
else:
log.warning("Codex 授权失败")
result["status"] = "auth_failed"
update_account_status(_tracker, team_name, email, "auth_failed")
save_team_tracker(_tracker)
elif register_success != "domain_blacklisted":
if is_team_owner_otp:
log.error(f"OTP 登录授权失败: {email}")
else:
log.error(f"注册/授权失败: {email}")
update_account_status(_tracker, team_name, email, "register_failed")
save_team_tracker(_tracker)
except ShutdownRequested: except ShutdownRequested:
# 用户请求停止,保存当前状态并退出 # 用户请求停止,保存当前状态并退出

42
uv.lock generated
View File

@@ -15,6 +15,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
] ]
[[package]]
name = "apscheduler"
version = "3.11.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "tzlocal" },
]
sdist = { url = "https://files.pythonhosted.org/packages/07/12/3e4389e5920b4c1763390c6d371162f3784f86f85cd6d6c1bfe68eef14e2/apscheduler-3.11.2.tar.gz", hash = "sha256:2a9966b052ec805f020c8c4c3ae6e6a06e24b1bf19f2e11d91d8cca0473eef41", size = 108683, upload-time = "2025-12-22T00:39:34.884Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9f/64/2e54428beba8d9992aa478bb8f6de9e4ecaa5f8f513bcfd567ed7fb0262d/apscheduler-3.11.2-py3-none-any.whl", hash = "sha256:ce005177f741409db4e4dd40a7431b76feb856b9dd69d57e0da49d6715bfd26d", size = 64439, upload-time = "2025-12-22T00:39:33.303Z" },
]
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2025.11.12" version = "2025.11.12"
@@ -326,7 +338,7 @@ version = "0.1.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "drissionpage" }, { name = "drissionpage" },
{ name = "python-telegram-bot" }, { name = "python-telegram-bot", extra = ["job-queue"] },
{ name = "requests" }, { name = "requests" },
{ name = "rich" }, { name = "rich" },
{ name = "setuptools" }, { name = "setuptools" },
@@ -337,7 +349,7 @@ dependencies = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "drissionpage", specifier = ">=4.1.1.2" }, { name = "drissionpage", specifier = ">=4.1.1.2" },
{ name = "python-telegram-bot", specifier = ">=22.5" }, { name = "python-telegram-bot", extras = ["job-queue"], specifier = ">=22.5" },
{ name = "requests", specifier = ">=2.32.5" }, { name = "requests", specifier = ">=2.32.5" },
{ name = "rich", specifier = ">=14.2.0" }, { name = "rich", specifier = ">=14.2.0" },
{ name = "setuptools", specifier = ">=80.9.0" }, { name = "setuptools", specifier = ">=80.9.0" },
@@ -404,6 +416,11 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/bc/c3/340c7520095a8c79455fcf699cbb207225e5b36490d2b9ee557c16a7b21b/python_telegram_bot-22.5-py3-none-any.whl", hash = "sha256:4b7cd365344a7dce54312cc4520d7fa898b44d1a0e5f8c74b5bd9b540d035d16", size = 730976, upload-time = "2025-09-27T13:50:25.93Z" }, { url = "https://files.pythonhosted.org/packages/bc/c3/340c7520095a8c79455fcf699cbb207225e5b36490d2b9ee557c16a7b21b/python_telegram_bot-22.5-py3-none-any.whl", hash = "sha256:4b7cd365344a7dce54312cc4520d7fa898b44d1a0e5f8c74b5bd9b540d035d16", size = 730976, upload-time = "2025-09-27T13:50:25.93Z" },
] ]
[package.optional-dependencies]
job-queue = [
{ name = "apscheduler" },
]
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.32.5" version = "2.32.5"
@@ -527,6 +544,27 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
] ]
[[package]]
name = "tzdata"
version = "2025.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
]
[[package]]
name = "tzlocal"
version = "5.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "tzdata", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761, upload-time = "2025-03-05T21:17:41.549Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" },
]
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "2.6.2" version = "2.6.2"