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:
@@ -482,6 +482,10 @@ func (c *CodexAPIAuth) ObtainAuthorizationCode() (string, error) {
|
||||
c.logStep(StepInputPassword, "验证密码...")
|
||||
|
||||
if pageType == "password" || strings.Contains(string(body), "password") {
|
||||
// 在密码验证前记录最新邮件ID (基于 get_code.go 的增强逻辑)
|
||||
latestEmailID := mail.GetLatestEmailID(c.email)
|
||||
c.logStep(StepInputPassword, "记录当前最新邮件ID: %d", latestEmailID)
|
||||
|
||||
// 5. 验证密码
|
||||
if !c.callSentinelReq("authorize_continue__auto") {
|
||||
return "", fmt.Errorf("Sentinel 请求失败")
|
||||
@@ -519,9 +523,9 @@ func (c *CodexAPIAuth) ObtainAuthorizationCode() (string, error) {
|
||||
if nextPageType == "email_otp_verification" {
|
||||
c.logStep(StepInputPassword, "账号需要邮箱验证,正在获取验证码...")
|
||||
|
||||
// 等待获取验证码 (最多60秒)
|
||||
// 邮件标题格式: "Your ChatGPT code is 016547"
|
||||
otpCode, err := mail.GetVerificationCode(c.email, 60*time.Second)
|
||||
// 使用邮件ID过滤获取新的OTP验证码 (最多30秒)
|
||||
// 基于 get_code.go 的增强逻辑:使用邮件ID过滤,避免获取到旧邮件的验证码
|
||||
otpCode, err := mail.GetEmailOTPAfterID(c.email, latestEmailID, 30*time.Second)
|
||||
if err != nil {
|
||||
c.logError(StepInputPassword, "获取验证码失败: %v", err)
|
||||
return "", fmt.Errorf("获取验证码失败: %v", err)
|
||||
@@ -558,6 +562,20 @@ func (c *CodexAPIAuth) ObtainAuthorizationCode() (string, error) {
|
||||
// 重新解析响应,检查下一步
|
||||
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 {
|
||||
c.logStep(StepInputPassword, "跳过密码验证步骤 (服务器未要求)")
|
||||
}
|
||||
@@ -656,6 +674,35 @@ func (c *CodexAPIAuth) ExchangeCodeForTokens(code, codeVerifier string) (*CodexT
|
||||
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 返回较小值
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
|
||||
@@ -129,6 +129,7 @@ type EmailListResponse struct {
|
||||
|
||||
// EmailItem 邮件项
|
||||
type EmailItem struct {
|
||||
EmailID int `json:"emailId"`
|
||||
Content string `json:"content"`
|
||||
Text string `json:"text"`
|
||||
Subject string `json:"subject"`
|
||||
@@ -504,3 +505,57 @@ func GetVerificationCode(email string, timeout time.Duration) (string, error) {
|
||||
client := NewClientForEmail(email)
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user