Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions src/autoteam/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ class SourceConfig(BaseModel):
"AUTO_CHECK_MIN_LOW",
"AUTO_CHECK_RETRY_ADD_PHONE",
"AUTO_CHECK_ADD_PHONE_MAX_RETRIES",
"AUTO_CHECK_SKIP_STANDBY_REUSE",
"PLAYWRIGHT_PROXY_URL",
"PLAYWRIGHT_PROXY_SERVER",
"PLAYWRIGHT_PROXY_USERNAME",
Expand Down Expand Up @@ -668,6 +669,7 @@ def _sync_runtime_globals():
AUTO_CHECK_INTERVAL,
AUTO_CHECK_MIN_LOW,
AUTO_CHECK_RETRY_ADD_PHONE,
AUTO_CHECK_SKIP_STANDBY_REUSE,
AUTO_CHECK_TARGET_SEATS,
AUTO_CHECK_THRESHOLD,
)
Expand All @@ -678,6 +680,7 @@ def _sync_runtime_globals():
auto_check_config["min_low"] = AUTO_CHECK_MIN_LOW
auto_check_config["retry_add_phone"] = AUTO_CHECK_RETRY_ADD_PHONE
auto_check_config["add_phone_max_retries"] = AUTO_CHECK_ADD_PHONE_MAX_RETRIES
auto_check_config["skip_standby_reuse"] = AUTO_CHECK_SKIP_STANDBY_REUSE
if auto_check_restart is not None:
auto_check_restart.set()
except Exception:
Expand Down Expand Up @@ -2281,16 +2284,17 @@ def _run():
from autoteam.accounts import STATUS_ACTIVE, update_account
from autoteam.codex_auth import (
check_codex_quota,
login_codex_via_browser,
quota_result_quota_info,
quota_result_resets_at,
save_auth_file,
)
from autoteam.mail_provider import get_mail_client_for_account
from autoteam.manager import _login_codex_with_result

mail_client = get_mail_client_for_account(acc)
mail_client.login()
bundle = login_codex_via_browser(email, acc.get("password", ""), mail_client=mail_client)
login_result = _login_codex_with_result(email, acc.get("password", ""), mail_client=mail_client)
bundle = login_result.get("bundle") if login_result.get("ok") else None
if bundle:
plan_type = str(bundle.get("plan_type") or "").lower()
if plan_type != "team":
Expand Down Expand Up @@ -2321,7 +2325,8 @@ def _run():

sync_to_cpa()
return {"email": email, "plan": bundle.get("plan_type"), "auth_file": auth_file}
raise RuntimeError(f"Codex 登录失败: {email}")
detail = login_result.get("error_detail") or login_result.get("error_type") or "登录失败"
raise RuntimeError(f"Codex 登录失败({detail}): {email}")

task = _start_task(f"login:{email}", _run, {"email": email})
return task
Expand Down Expand Up @@ -2742,6 +2747,9 @@ def cancel_task(task_id: str):
from autoteam.config import (
AUTO_CHECK_RETRY_ADD_PHONE as _DEFAULT_RETRY_ADD_PHONE,
)
from autoteam.config import (
AUTO_CHECK_SKIP_STANDBY_REUSE as _DEFAULT_SKIP_STANDBY_REUSE,
)
from autoteam.config import (
AUTO_CHECK_TARGET_SEATS as _DEFAULT_TARGET_SEATS,
)
Expand All @@ -2757,6 +2765,7 @@ def cancel_task(task_id: str):
"min_low": _DEFAULT_MIN_LOW,
"retry_add_phone": _DEFAULT_RETRY_ADD_PHONE,
"add_phone_max_retries": _DEFAULT_ADD_PHONE_MAX_RETRIES,
"skip_standby_reuse": _DEFAULT_SKIP_STANDBY_REUSE,
}
_auto_check_stop = threading.Event()
_auto_check_restart = threading.Event() # 配置变更时通知线程重启
Expand Down Expand Up @@ -3257,6 +3266,7 @@ class AutoCheckConfig(BaseModel):
min_low: int = 2 # 触发轮转的最少账号数
retry_add_phone: bool = True # 是否自动重试 add_phone
add_phone_max_retries: int = 3 # add_phone 最大自动重试次数
skip_standby_reuse: bool = False # 跳过旧账号复用,直接注册新账号


def _normalized_auto_check_config(cfg: AutoCheckConfig | dict[str, object]) -> dict[str, int | bool]:
Expand All @@ -3267,6 +3277,7 @@ def _normalized_auto_check_config(cfg: AutoCheckConfig | dict[str, object]) -> d
min_low = cfg.min_low
retry_add_phone = cfg.retry_add_phone
add_phone_max_retries = cfg.add_phone_max_retries
skip_standby_reuse = cfg.skip_standby_reuse
else:
interval = cfg.get("interval", _auto_check_config.get("interval", _DEFAULT_INTERVAL))
target_seats = cfg.get("target_seats", _auto_check_config.get("target_seats", _DEFAULT_TARGET_SEATS))
Expand All @@ -3279,6 +3290,10 @@ def _normalized_auto_check_config(cfg: AutoCheckConfig | dict[str, object]) -> d
"add_phone_max_retries",
_auto_check_config.get("add_phone_max_retries", _DEFAULT_ADD_PHONE_MAX_RETRIES),
)
skip_standby_reuse = cfg.get(
"skip_standby_reuse",
_auto_check_config.get("skip_standby_reuse", _DEFAULT_SKIP_STANDBY_REUSE),
)

return {
"interval": max(60, int(interval)),
Expand All @@ -3287,6 +3302,7 @@ def _normalized_auto_check_config(cfg: AutoCheckConfig | dict[str, object]) -> d
"min_low": max(1, int(min_low)),
"retry_add_phone": bool(retry_add_phone),
"add_phone_max_retries": max(1, int(add_phone_max_retries)),
"skip_standby_reuse": bool(skip_standby_reuse),
}


Expand All @@ -3295,6 +3311,7 @@ def get_auto_check_config():
"""获取巡检配置"""
cfg = _auto_check_config.copy()
cfg.setdefault("target_seats", _DEFAULT_TARGET_SEATS)
cfg.setdefault("skip_standby_reuse", _DEFAULT_SKIP_STANDBY_REUSE)
return cfg


Expand All @@ -3313,6 +3330,7 @@ def set_auto_check_config(cfg: AutoCheckConfig):
"AUTO_CHECK_MIN_LOW": str(normalized["min_low"]),
"AUTO_CHECK_RETRY_ADD_PHONE": "true" if normalized["retry_add_phone"] else "false",
"AUTO_CHECK_ADD_PHONE_MAX_RETRIES": str(normalized["add_phone_max_retries"]),
"AUTO_CHECK_SKIP_STANDBY_REUSE": "true" if normalized["skip_standby_reuse"] else "false",
}
for key, value in persisted.items():
os.environ[key] = value
Expand All @@ -3321,13 +3339,14 @@ def set_auto_check_config(cfg: AutoCheckConfig):
_sync_runtime_env_reload_state()
_auto_check_restart.set() # 唤醒巡检线程,立即应用新配置
logger.info(
"[巡检] 配置已更新并持久化: 间隔=%ds 目标seat=%d 阈值=%d%% 触发=%d个 add_phone自动重试=%s 最大重试=%d",
"[巡检] 配置已更新并持久化: 间隔=%ds 目标seat=%d 阈值=%d%% 触发=%d个 add_phone自动重试=%s 最大重试=%d 跳过复用=%s",
_auto_check_config["interval"],
_auto_check_config["target_seats"],
_auto_check_config["threshold"],
_auto_check_config["min_low"],
"开" if _auto_check_config["retry_add_phone"] else "关",
_auto_check_config["add_phone_max_retries"],
"开" if _auto_check_config["skip_standby_reuse"] else "关",
)
return _auto_check_config.copy()

Expand Down
12 changes: 12 additions & 0 deletions src/autoteam/codex_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ def _classify_oauth_failure(url, body_excerpt=""):
url = (url or "").lower()
body = (body_excerpt or "").lower()

if "no_valid_organizations" in url or "no_valid_organizations" in body:
return "no_valid_organizations", "账号暂无有效 organization(可能 provisioning 未完成)", True
if "add-phone" in url:
return "add_phone", "需要手机号验证", False
if "choose-an-account" in url:
Expand Down Expand Up @@ -931,17 +933,25 @@ def login_codex_via_browser(
logger.info("[Codex] 先登录 ChatGPT 选择 Team workspace...")
_page = context.new_page()
_page.goto("https://chatgpt.com/auth/login", wait_until="domcontentloaded", timeout=60000)
logger.info("[Codex] ChatGPT 登录页已加载: %s", _page.url)
time.sleep(5)

# Cloudflare
cf_hit = False
for _i in range(12):
if "verify you are human" not in _page.content()[:2000].lower():
if cf_hit:
logger.info("[Codex] Cloudflare 已通过")
break
if not cf_hit:
logger.info("[Codex] 检测到 Cloudflare 人机验证,等待...")
cf_hit = True
time.sleep(5)

# 点击登录
try:
_page.locator('button:has-text("登录"), button:has-text("Log in")').first.click()
logger.info("[Codex] 已点击登录按钮")
time.sleep(3)
except Exception:
pass
Expand All @@ -953,6 +963,7 @@ def login_codex_via_browser(
ei.fill(email)
time.sleep(0.5)
_click_primary_auth_button(_page, ei, ["Continue", "继续"])
logger.info("[Codex] 邮箱已提交,等待密码/验证码页...")
time.sleep(3)
except Exception:
pass
Expand All @@ -965,6 +976,7 @@ def login_codex_via_browser(
pi.fill(password)
time.sleep(0.5)
_click_primary_auth_button(_page, pi, ["Continue", "继续", "Log in"])
logger.info("[Codex] 密码已提交")
else:
# 没有密码,点击"使用一次性验证码登录"
otp_btn = _page.locator(
Expand Down
3 changes: 3 additions & 0 deletions src/autoteam/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ def _normalize_chatgpt_api_transport(value: str) -> str:
AUTO_CHECK_MIN_LOW = _get_int_env("AUTO_CHECK_MIN_LOW", 2) # 至少几个账号低于阈值才触发,默认 2
AUTO_CHECK_RETRY_ADD_PHONE = _get_bool_env("AUTO_CHECK_RETRY_ADD_PHONE", True) # 是否自动重试 add_phone
AUTO_CHECK_ADD_PHONE_MAX_RETRIES = _get_int_env("AUTO_CHECK_ADD_PHONE_MAX_RETRIES", 3) # add_phone 最大自动重试次数
AUTO_CHECK_SKIP_STANDBY_REUSE = _get_bool_env(
"AUTO_CHECK_SKIP_STANDBY_REUSE", False
) # 轮转时跳过复用旧账号,直接注册新账号

# Playwright 代理配置
PLAYWRIGHT_PROXY_URL = os.environ.get("PLAYWRIGHT_PROXY_URL", "").strip()
Expand Down
Loading
Loading