From 5a3b3aa8ef763f6616cae241e50a58cd861b5dd3 Mon Sep 17 00:00:00 2001 From: kyx236 Date: Mon, 2 Feb 2026 05:54:55 +0800 Subject: [PATCH] feat: Implement S2A OAuth authorization using chromedp and introduce new files for browser automation and team processing. --- backend/internal/api/team_process.go | 58 +++++++++++++++++++++------- backend/internal/auth/chromedp.go | 53 +++++++++++++++++-------- backend/internal/auth/rod.go | 32 ++++++++++++--- 3 files changed, 108 insertions(+), 35 deletions(-) diff --git a/backend/internal/api/team_process.go b/backend/internal/api/team_process.go index e3724bb..3d730ef 100644 --- a/backend/internal/api/team_process.go +++ b/backend/internal/api/team_process.go @@ -902,23 +902,53 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul return result } -// registerWithTimeout 带超时的注册 +// registerWithTimeout 带超时的注册(遇到 403 会换指纹重试) func registerWithTimeout(email, password, name, birthdate, proxy string) (*register.ChatGPTReg, error) { - reg, err := register.New(proxy) - if err != nil { - return nil, err + const maxInitRetries = 3 + var reg *register.ChatGPTReg + var initErr error + + // 初始化阶段:遇到 403 换指纹重试 + for attempt := 0; attempt < maxInitRetries; attempt++ { + var err error + reg, err = register.New(proxy) + if err != nil { + return nil, err + } + + if err := reg.InitSession(); err != nil { + initErr = err + // 检查是否是 403 错误,换指纹重试 + if strings.Contains(err.Error(), "403") { + continue + } + return nil, fmt.Errorf("初始化失败: %v", err) + } + + if err := reg.GetAuthorizeURL(email); err != nil { + // 403 也可能在这里出现 + if strings.Contains(err.Error(), "403") { + initErr = err + continue + } + return nil, fmt.Errorf("获取授权URL失败: %v", err) + } + + if err := reg.StartAuthorize(); err != nil { + if strings.Contains(err.Error(), "403") { + initErr = err + continue + } + return nil, fmt.Errorf("启动授权失败: %v", err) + } + + // 初始化成功,跳出重试循环 + initErr = nil + break } - if err := reg.InitSession(); err != nil { - return nil, fmt.Errorf("初始化失败: %v", err) - } - - if err := reg.GetAuthorizeURL(email); err != nil { - return nil, fmt.Errorf("获取授权URL失败: %v", err) - } - - if err := reg.StartAuthorize(); err != nil { - return nil, fmt.Errorf("启动授权失败: %v", err) + if initErr != nil { + return nil, fmt.Errorf("初始化失败(重试%d次): %v", maxInitRetries, initErr) } if err := reg.Register(email, password); err != nil { diff --git a/backend/internal/auth/chromedp.go b/backend/internal/auth/chromedp.go index 42452bc..9b1e5e0 100644 --- a/backend/internal/auth/chromedp.go +++ b/backend/internal/auth/chromedp.go @@ -8,6 +8,7 @@ import ( "codex-pool/internal/proxyutil" + "github.com/chromedp/cdproto/cdp" "github.com/chromedp/cdproto/fetch" "github.com/chromedp/cdproto/network" "github.com/chromedp/cdproto/page" @@ -226,22 +227,7 @@ func CompleteWithChromedpLogged(authURL, email, password, teamID string, headles } } - time.Sleep(1500 * time.Millisecond) - - if callbackURL != "" { - logStep(StepComplete, "授权成功") - return ExtractCodeFromCallbackURL(callbackURL), nil - } - - _ = chromedp.Run(ctx, chromedp.Location(¤tURL)) - if strings.Contains(currentURL, "code=") { - logStep(StepComplete, "授权成功") - return ExtractCodeFromCallbackURL(currentURL), nil - } - - logStep(StepInputPassword, "查找密码框 | URL: %s", currentURL) - - // 密码输入框选择器 + // 等待页面跳转(等待 URL 变化或密码框出现,最多 10 秒) passwordSelectors := []string{ `input[name="current-password"]`, `input[autocomplete="current-password"]`, @@ -250,6 +236,41 @@ func CompleteWithChromedpLogged(authURL, email, password, teamID string, headles `input[id="password"]`, } + var passwordFound bool + for i := 0; i < 20; i++ { + time.Sleep(500 * time.Millisecond) + + if callbackURL != "" { + logStep(StepComplete, "授权成功") + return ExtractCodeFromCallbackURL(callbackURL), nil + } + + _ = chromedp.Run(ctx, chromedp.Location(¤tURL)) + if strings.Contains(currentURL, "code=") { + logStep(StepComplete, "授权成功") + return ExtractCodeFromCallbackURL(currentURL), nil + } + + // 检查是否有密码输入框(页面已跳转) + for _, sel := range passwordSelectors { + var nodes []*cdp.Node + if err := chromedp.Run(ctx, chromedp.Nodes(sel, &nodes, chromedp.ByQuery)); err == nil && len(nodes) > 0 { + passwordFound = true + break + } + } + if passwordFound { + break + } + + // 如果 URL 已经变化(包含 password),也跳出 + if strings.Contains(currentURL, "password") { + break + } + } + + logStep(StepInputPassword, "查找密码框 | URL: %s", currentURL) + // 使用短超时查找密码框(10秒) findCtx2, findCancel2 := context.WithTimeout(ctx, 10*time.Second) diff --git a/backend/internal/auth/rod.go b/backend/internal/auth/rod.go index ea0ea28..8a5568c 100644 --- a/backend/internal/auth/rod.go +++ b/backend/internal/auth/rod.go @@ -245,11 +245,28 @@ func (r *RodAuth) CompleteOAuthLogged(authURL, email, password, teamID string, l btn.MustClick() } - time.Sleep(1500 * time.Millisecond) + // 等待页面跳转(等待 URL 变化或密码框出现,最多 10 秒) + passwordSelector := "input[name='current-password'], input[autocomplete='current-password'], input[type='password'], input[name='password'], input[id='password']" + var passwordFound bool + for i := 0; i < 20; i++ { + time.Sleep(500 * time.Millisecond) - if code := r.checkForCode(page); code != "" { - logStep(StepComplete, "授权成功") - return code, nil + if code := r.checkForCode(page); code != "" { + logStep(StepComplete, "授权成功") + return code, nil + } + + info, _ = page.Info() + // 检查是否有密码输入框(页面已跳转) + if pwdInput, _ := page.Timeout(100 * time.Millisecond).Element(passwordSelector); pwdInput != nil { + passwordFound = true + break + } + + // 如果 URL 已经变化(包含 password),也跳出 + if strings.Contains(info.URL, "password") { + break + } } // 获取当前URL用于调试 @@ -257,7 +274,12 @@ func (r *RodAuth) CompleteOAuthLogged(authURL, email, password, teamID string, l logStep(StepInputPassword, "查找密码框 | URL: %s", info.URL) // 使用10秒超时查找密码输入框(优先使用 current-password) - passwordInput, err := page.Timeout(10 * time.Second).Element("input[name='current-password'], input[autocomplete='current-password'], input[type='password'], input[name='password'], input[id='password']") + var passwordInput *rod.Element + if passwordFound { + passwordInput, err = page.Timeout(2 * time.Second).Element(passwordSelector) + } else { + passwordInput, err = page.Timeout(10 * time.Second).Element(passwordSelector) + } if err != nil { info, _ := page.Info() logError(StepInputPassword, "未找到密码输入框 | URL: %s", info.URL)