feat: Implement browserless Codex API authentication with PoW solving and a new mail service, and update gitignore to exclude get_code.go.

This commit is contained in:
2026-02-04 09:43:22 +08:00
parent 381cbd41fa
commit 61712cf4fb
3 changed files with 106 additions and 3 deletions

1
.gitignore vendored
View File

@@ -103,3 +103,4 @@ backend/codex-pool.exe
.claude/settings.local.json .claude/settings.local.json
CodexAuth CodexAuth
get_code.go

View File

@@ -482,6 +482,10 @@ func (c *CodexAPIAuth) ObtainAuthorizationCode() (string, error) {
c.logStep(StepInputPassword, "验证密码...") c.logStep(StepInputPassword, "验证密码...")
if pageType == "password" || strings.Contains(string(body), "password") { if pageType == "password" || strings.Contains(string(body), "password") {
// 在密码验证前记录最新邮件ID (基于 get_code.go 的增强逻辑)
latestEmailID := mail.GetLatestEmailID(c.email)
c.logStep(StepInputPassword, "记录当前最新邮件ID: %d", latestEmailID)
// 5. 验证密码 // 5. 验证密码
if !c.callSentinelReq("authorize_continue__auto") { if !c.callSentinelReq("authorize_continue__auto") {
return "", fmt.Errorf("Sentinel 请求失败") return "", fmt.Errorf("Sentinel 请求失败")
@@ -519,9 +523,9 @@ func (c *CodexAPIAuth) ObtainAuthorizationCode() (string, error) {
if nextPageType == "email_otp_verification" { if nextPageType == "email_otp_verification" {
c.logStep(StepInputPassword, "账号需要邮箱验证,正在获取验证码...") c.logStep(StepInputPassword, "账号需要邮箱验证,正在获取验证码...")
// 等待获取验证码 (最多60秒) // 使用邮件ID过滤获取新的OTP验证码 (最多30秒)
// 邮件标题格式: "Your ChatGPT code is 016547" // 基于 get_code.go 的增强逻辑使用邮件ID过滤避免获取到旧邮件的验证码
otpCode, err := mail.GetVerificationCode(c.email, 60*time.Second) otpCode, err := mail.GetEmailOTPAfterID(c.email, latestEmailID, 30*time.Second)
if err != nil { if err != nil {
c.logError(StepInputPassword, "获取验证码失败: %v", err) c.logError(StepInputPassword, "获取验证码失败: %v", err)
return "", fmt.Errorf("获取验证码失败: %v", err) return "", fmt.Errorf("获取验证码失败: %v", err)
@@ -558,6 +562,20 @@ func (c *CodexAPIAuth) ObtainAuthorizationCode() (string, error) {
// 重新解析响应,检查下一步 // 重新解析响应,检查下一步
json.Unmarshal(body, &data) json.Unmarshal(body, &data)
} }
// 基于 get_code.go 的增强逻辑:检查是否有 continue_url
// 如果有,直接跟随重定向获取授权码,跳过工作区选择
if cu, ok := data["continue_url"].(string); ok && cu != "" && !strings.Contains(cu, "email-verification") {
if strings.Contains(cu, "consent") {
// 访问 consent 页面
c.logStep(StepSelectWorkspace, "处理 consent 页面...")
c.doRequest("GET", cu, nil, headers)
} else {
// 直接跟随重定向获取授权码
c.logStep(StepWaitCallback, "跟随 continue_url 重定向...")
return c.followRedirectsForCode(cu, headers)
}
}
} else { } else {
c.logStep(StepInputPassword, "跳过密码验证步骤 (服务器未要求)") c.logStep(StepInputPassword, "跳过密码验证步骤 (服务器未要求)")
} }
@@ -656,6 +674,35 @@ func (c *CodexAPIAuth) ExchangeCodeForTokens(code, codeVerifier string) (*CodexT
return &tokens, nil return &tokens, nil
} }
// followRedirectsForCode 跟随重定向获取授权码
// 基于 get_code.go 的实现,用于复用重定向逻辑
func (c *CodexAPIAuth) followRedirectsForCode(continueURL string, headers map[string]string) (string, error) {
c.logStep(StepWaitCallback, "跟随重定向获取授权码...")
for i := 0; i < 10; i++ {
resp, _, err := c.doRequest("GET", continueURL, nil, headers)
if err != nil {
break
}
if resp.StatusCode >= 300 && resp.StatusCode < 400 {
location := resp.Header.Get("Location")
if strings.Contains(location, "localhost:1455") {
code := ExtractCodeFromCallbackURL(location)
if code != "" {
c.logStep(StepComplete, "授权成功,获取到授权码")
return code, nil
}
}
continueURL = location
} else {
break
}
}
c.logError(StepWaitCallback, "未能获取授权码")
return "", fmt.Errorf("未能获取授权码")
}
// min 返回较小值 // min 返回较小值
func min(a, b int) int { func min(a, b int) int {
if a < b { if a < b {

View File

@@ -129,6 +129,7 @@ type EmailListResponse struct {
// EmailItem 邮件项 // EmailItem 邮件项
type EmailItem struct { type EmailItem struct {
EmailID int `json:"emailId"`
Content string `json:"content"` Content string `json:"content"`
Text string `json:"text"` Text string `json:"text"`
Subject string `json:"subject"` Subject string `json:"subject"`
@@ -504,3 +505,57 @@ func GetVerificationCode(email string, timeout time.Duration) (string, error) {
client := NewClientForEmail(email) client := NewClientForEmail(email)
return client.WaitForCode(email, timeout) return client.WaitForCode(email, timeout)
} }
// GetLatestEmailID 获取邮箱最新邮件的ID
// 基于 get_code.go 的实现用于在发送验证码请求前记录最新邮件ID
func GetLatestEmailID(email string) int {
client := NewClientForEmail(email)
emails, err := client.GetEmails(email, 1)
if err != nil || len(emails) == 0 {
return 0
}
return emails[0].EmailID
}
// GetEmailOTPAfterID 获取指定邮件ID之后的OTP验证码
// 基于 get_code.go 的实现,只获取 afterEmailID 之后的新邮件中的验证码
func GetEmailOTPAfterID(email string, afterEmailID int, timeout time.Duration) (string, error) {
client := NewClientForEmail(email)
start := time.Now()
codeRegex := regexp.MustCompile(`\b(\d{6})\b`)
for time.Since(start) < timeout {
emails, err := client.GetEmails(email, 5)
if err == nil {
for _, mail := range emails {
// 只处理 afterEmailID 之后的新邮件
if mail.EmailID <= afterEmailID {
continue
}
subject := strings.ToLower(mail.Subject)
// 匹配 OpenAI/ChatGPT 验证码邮件
if !strings.Contains(subject, "code") {
continue
}
// 从标题提取验证码
if matches := codeRegex.FindStringSubmatch(mail.Subject); len(matches) >= 2 {
return matches[1], nil
}
// 从内容提取验证码
content := mail.Content
if content == "" {
content = mail.Text
}
if matches := codeRegex.FindStringSubmatch(content); len(matches) >= 2 {
return matches[1], nil
}
}
}
time.Sleep(1 * time.Second)
}
return "", fmt.Errorf("验证码获取超时 (afterEmailID=%d)", afterEmailID)
}