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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -103,3 +103,4 @@ backend/codex-pool.exe
|
|||||||
.claude/settings.local.json
|
.claude/settings.local.json
|
||||||
CodexAuth
|
CodexAuth
|
||||||
|
|
||||||
|
get_code.go
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user