feat: Implement S2A OAuth authorization using chromedp and introduce new files for browser automation and team processing.

This commit is contained in:
2026-02-02 05:54:55 +08:00
parent 74796a16dd
commit 5a3b3aa8ef
3 changed files with 108 additions and 35 deletions

View File

@@ -902,23 +902,53 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
return result return result
} }
// registerWithTimeout 带超时的注册 // registerWithTimeout 带超时的注册(遇到 403 会换指纹重试)
func registerWithTimeout(email, password, name, birthdate, proxy string) (*register.ChatGPTReg, error) { func registerWithTimeout(email, password, name, birthdate, proxy string) (*register.ChatGPTReg, error) {
reg, err := register.New(proxy) const maxInitRetries = 3
if err != nil { var reg *register.ChatGPTReg
return nil, err 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 { if initErr != nil {
return nil, fmt.Errorf("初始化失败: %v", err) return nil, fmt.Errorf("初始化失败(重试%d次): %v", maxInitRetries, initErr)
}
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 err := reg.Register(email, password); err != nil { if err := reg.Register(email, password); err != nil {

View File

@@ -8,6 +8,7 @@ import (
"codex-pool/internal/proxyutil" "codex-pool/internal/proxyutil"
"github.com/chromedp/cdproto/cdp"
"github.com/chromedp/cdproto/fetch" "github.com/chromedp/cdproto/fetch"
"github.com/chromedp/cdproto/network" "github.com/chromedp/cdproto/network"
"github.com/chromedp/cdproto/page" "github.com/chromedp/cdproto/page"
@@ -226,22 +227,7 @@ func CompleteWithChromedpLogged(authURL, email, password, teamID string, headles
} }
} }
time.Sleep(1500 * time.Millisecond) // 等待页面跳转(等待 URL 变化或密码框出现,最多 10 秒)
if callbackURL != "" {
logStep(StepComplete, "授权成功")
return ExtractCodeFromCallbackURL(callbackURL), nil
}
_ = chromedp.Run(ctx, chromedp.Location(&currentURL))
if strings.Contains(currentURL, "code=") {
logStep(StepComplete, "授权成功")
return ExtractCodeFromCallbackURL(currentURL), nil
}
logStep(StepInputPassword, "查找密码框 | URL: %s", currentURL)
// 密码输入框选择器
passwordSelectors := []string{ passwordSelectors := []string{
`input[name="current-password"]`, `input[name="current-password"]`,
`input[autocomplete="current-password"]`, `input[autocomplete="current-password"]`,
@@ -250,6 +236,41 @@ func CompleteWithChromedpLogged(authURL, email, password, teamID string, headles
`input[id="password"]`, `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(&currentURL))
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秒 // 使用短超时查找密码框10秒
findCtx2, findCancel2 := context.WithTimeout(ctx, 10*time.Second) findCtx2, findCancel2 := context.WithTimeout(ctx, 10*time.Second)

View File

@@ -245,11 +245,28 @@ func (r *RodAuth) CompleteOAuthLogged(authURL, email, password, teamID string, l
btn.MustClick() 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 != "" { if code := r.checkForCode(page); code != "" {
logStep(StepComplete, "授权成功") logStep(StepComplete, "授权成功")
return code, nil 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用于调试 // 获取当前URL用于调试
@@ -257,7 +274,12 @@ func (r *RodAuth) CompleteOAuthLogged(authURL, email, password, teamID string, l
logStep(StepInputPassword, "查找密码框 | URL: %s", info.URL) logStep(StepInputPassword, "查找密码框 | URL: %s", info.URL)
// 使用10秒超时查找密码输入框优先使用 current-password // 使用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 { if err != nil {
info, _ := page.Info() info, _ := page.Info()
logError(StepInputPassword, "未找到密码输入框 | URL: %s", info.URL) logError(StepInputPassword, "未找到密码输入框 | URL: %s", info.URL)