feat: add mail service for managing email configurations, generating accounts, and fetching emails with verification code support.
This commit is contained in:
@@ -16,6 +16,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"codex-pool/internal/mail"
|
||||
|
||||
utls "github.com/refraction-networking/utls"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
@@ -513,17 +515,61 @@ func (c *CodexAPIAuth) ObtainAuthorizationCode() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 如果需要邮箱验证,这是新账号的问题
|
||||
// 如果需要邮箱验证,尝试获取验证码并完成验证
|
||||
if nextPageType == "email_otp_verification" {
|
||||
c.logError(StepInputPassword, "账号需要邮箱验证,无法继续 Codex 授权流程")
|
||||
return "", fmt.Errorf("账号需要邮箱验证,请使用浏览器模式或等待账号状态更新")
|
||||
c.logStep(StepInputPassword, "账号需要邮箱验证,正在获取验证码...")
|
||||
|
||||
// 等待获取验证码 (最多60秒)
|
||||
// 邮件标题格式: "Your ChatGPT code is 016547"
|
||||
otpCode, err := mail.GetVerificationCode(c.email, 60*time.Second)
|
||||
if err != nil {
|
||||
c.logError(StepInputPassword, "获取验证码失败: %v", err)
|
||||
return "", fmt.Errorf("获取验证码失败: %v", err)
|
||||
}
|
||||
c.logStep(StepInputPassword, "获取到验证码: %s", otpCode)
|
||||
|
||||
// 提交验证码到 /api/accounts/email-otp/validate
|
||||
// 先获取 Sentinel token (可能需要 PoW)
|
||||
if !c.callSentinelReq("email_otp_verification__auto") {
|
||||
// 如果失败,尝试 password_verify__auto
|
||||
if !c.callSentinelReq("password_verify__auto") {
|
||||
return "", fmt.Errorf("Sentinel 请求失败")
|
||||
}
|
||||
}
|
||||
|
||||
verifyOtpHeaders := make(map[string]string)
|
||||
for k, v := range headers {
|
||||
verifyOtpHeaders[k] = v
|
||||
}
|
||||
verifyOtpHeaders["OpenAI-Sentinel-Token"] = c.getSentinelHeader("email_otp_validate")
|
||||
|
||||
// 请求体格式: {"code":"016547"}
|
||||
otpPayload := map[string]string{
|
||||
"code": otpCode,
|
||||
}
|
||||
|
||||
resp, body, err = c.doRequest("POST", "https://auth.openai.com/api/accounts/email-otp/validate", otpPayload, verifyOtpHeaders)
|
||||
if err != nil || resp.StatusCode != 200 {
|
||||
c.logError(StepInputPassword, "验证码验证失败: %d - %s", resp.StatusCode, string(body[:min(200, len(body))]))
|
||||
return "", fmt.Errorf("验证码验证失败: %d", resp.StatusCode)
|
||||
}
|
||||
c.logStep(StepInputPassword, "邮箱验证成功")
|
||||
|
||||
// 重新解析响应,检查下一步
|
||||
json.Unmarshal(body, &data)
|
||||
}
|
||||
} else {
|
||||
c.logStep(StepInputPassword, "跳过密码验证步骤 (服务器未要求)")
|
||||
}
|
||||
|
||||
// 6. 选择工作区
|
||||
c.logStep(StepSelectWorkspace, "选择工作区: %s", c.workspaceID)
|
||||
if !c.callSentinelReq("password_verify__auto") {
|
||||
return "", fmt.Errorf("Sentinel 请求失败")
|
||||
// 根据前面的流程选择正确的 Sentinel 请求
|
||||
if !c.callSentinelReq("email_otp_validate__auto") {
|
||||
// 如果 email_otp_validate__auto 失败,尝试 password_verify__auto
|
||||
if !c.callSentinelReq("password_verify__auto") {
|
||||
return "", fmt.Errorf("Sentinel 请求失败")
|
||||
}
|
||||
}
|
||||
|
||||
// 选择工作区时带上 Sentinel Header
|
||||
|
||||
@@ -363,21 +363,30 @@ func (m *Client) WaitForCode(email string, timeout time.Duration) (string, error
|
||||
for _, mail := range emails {
|
||||
subject := strings.ToLower(mail.Subject)
|
||||
// 匹配多种可能的验证码邮件主题
|
||||
// 包括 "Your ChatGPT code is 016547" 格式
|
||||
isCodeEmail := strings.Contains(subject, "code") ||
|
||||
strings.Contains(subject, "verify") ||
|
||||
strings.Contains(subject, "verification") ||
|
||||
strings.Contains(subject, "openai") ||
|
||||
strings.Contains(subject, "confirm")
|
||||
strings.Contains(subject, "confirm") ||
|
||||
strings.Contains(subject, "chatgpt")
|
||||
|
||||
if !isCodeEmail {
|
||||
continue
|
||||
}
|
||||
|
||||
// 优先从标题中提取验证码 (如 "Your ChatGPT code is 016547")
|
||||
matches := codeRegex.FindStringSubmatch(mail.Subject)
|
||||
if len(matches) >= 2 {
|
||||
return matches[1], nil
|
||||
}
|
||||
|
||||
// 如果标题中没有,从内容中提取
|
||||
content := mail.Content
|
||||
if content == "" {
|
||||
content = mail.Text
|
||||
}
|
||||
matches := codeRegex.FindStringSubmatch(content)
|
||||
matches = codeRegex.FindStringSubmatch(content)
|
||||
if len(matches) >= 2 {
|
||||
return matches[1], nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user