feat: Introduce advanced TLS client with browser fingerprinting and new backend modules for API processing, authentication, mail, and ChatGPT registration.

This commit is contained in:
2026-02-06 18:49:55 +08:00
parent 4ac7290e1f
commit 98ac10987c
5 changed files with 130 additions and 32 deletions

View File

@@ -67,6 +67,18 @@ type TeamProcessState struct {
var teamProcessState = &TeamProcessState{} var teamProcessState = &TeamProcessState{}
// waitGroupWithTimeout 带超时的 WaitGroup 等待,超时返回 false
func waitGroupWithTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {
done := make(chan struct{})
go func() { wg.Wait(); close(done) }()
select {
case <-done:
return true
case <-time.After(timeout):
return false
}
}
// getProxyDisplay 获取代理显示名称(隐藏密码) // getProxyDisplay 获取代理显示名称(隐藏密码)
func getProxyDisplay(proxy string) string { func getProxyDisplay(proxy string) string {
if proxy == "" { if proxy == "" {
@@ -648,8 +660,17 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
memberLogPrefix := fmt.Sprintf("%s [Member %d]", logPrefix, memberIdx+1) memberLogPrefix := fmt.Sprintf("%s [Member %d]", logPrefix, memberIdx+1)
memberStartTime := time.Now() memberStartTime := time.Now()
// 获取入库信号量 // 获取入库信号量3分钟超时
s2aSem <- struct{}{} select {
case s2aSem <- struct{}{}:
case <-time.After(3 * time.Minute):
logger.Warning(fmt.Sprintf("%s 入库信号量等待超时 (3分钟)", memberLogPrefix), memberEmail, "team")
atomic.AddInt32(&s2aFailCount, 1)
memberMu.Lock()
result.Errors = append(result.Errors, fmt.Sprintf("成员 %d 入库信号量超时", memberIdx+1))
memberMu.Unlock()
return false
}
defer func() { <-s2aSem }() defer func() { <-s2aSem }()
// 从代理池获取随机代理(默认轮询使用代理池,无代理则直连) // 从代理池获取随机代理(默认轮询使用代理池,无代理则直连)
@@ -764,23 +785,29 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
currentEmail := email currentEmail := email
currentPassword := password currentPassword := password
if attempt > 0 { if attempt > 0 {
// 重试时使用新邮箱 // 注册失败重试:保持原邮箱(邀请已发送),仅重新注册
currentEmail = mail.GenerateEmail() logger.Warning(fmt.Sprintf("%s 注册重试 (第%d次), 保持邮箱: %s", memberLogPrefix, attempt+1, currentEmail), currentEmail, "team")
currentPassword = register.GeneratePassword()
logger.Warning(fmt.Sprintf("%s 重试 (第%d次), 新邮箱: %s", memberLogPrefix, attempt+1, currentEmail), currentEmail, "team")
} }
// 发送邀请 // 首次尝试时发送邀请,重试时跳过(邀请已发送到该邮箱)
if err := inviter.SendInvites([]string{currentEmail}); err != nil { if attempt == 0 {
errStr := err.Error() if err := inviter.SendInvites([]string{currentEmail}); err != nil {
logger.Error(fmt.Sprintf("%s 邀请失败: %v", memberLogPrefix, err), currentEmail, "team") errStr := err.Error()
logger.Error(fmt.Sprintf("%s 邀请失败: %v", memberLogPrefix, err), currentEmail, "team")
// 检测 Team 已达邀请上限401 或 maximum number of seats // 检测 Team 已达邀请上限401 或 maximum number of seats
if strings.Contains(errStr, "401") || strings.Contains(errStr, "maximum number of seats") { if strings.Contains(errStr, "401") || strings.Contains(errStr, "maximum number of seats") {
markTeamExhausted() markTeamExhausted()
return false return false
}
// 邀请失败时换新邮箱重试
email = mail.GenerateEmail()
password = register.GeneratePassword()
currentEmail = email
currentPassword = password
logger.Warning(fmt.Sprintf("%s 邀请失败,换新邮箱: %s", memberLogPrefix, currentEmail), currentEmail, "team")
continue
} }
continue
} }
// 再次检查是否应该停止(邀请期间其他 goroutine 可能已标记) // 再次检查是否应该停止(邀请期间其他 goroutine 可能已标记)
@@ -841,11 +868,15 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
registerAndS2AMember(idx, email, password) registerAndS2AMember(idx, email, password)
}(i) }(i)
} }
regWg.Wait() if !waitGroupWithTimeout(&regWg, 8*time.Minute) {
logger.Warning(fmt.Sprintf("%s 注册阶段超时 (8分钟),继续处理已完成的成员", logPrefix), owner.Email, "team")
}
// 如果 Team 已满,等待已启动的入库完成 // 如果 Team 已满,等待已启动的入库完成
if isTeamExhausted() { if isTeamExhausted() {
s2aWg.Wait() if !waitGroupWithTimeout(&s2aWg, 5*time.Minute) {
logger.Warning(fmt.Sprintf("%s 入库等待超时 (5分钟)", logPrefix), owner.Email, "team")
}
result.AddedToS2A = int(atomic.LoadInt32(&s2aSuccessCount)) result.AddedToS2A = int(atomic.LoadInt32(&s2aSuccessCount))
result.Errors = append(result.Errors, "Team 邀请已满") result.Errors = append(result.Errors, "Team 邀请已满")
result.DurationMs = time.Since(startTime).Milliseconds() result.DurationMs = time.Since(startTime).Milliseconds()
@@ -874,7 +905,9 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
} }
// 等待所有入库完成 // 等待所有入库完成
s2aWg.Wait() if !waitGroupWithTimeout(&s2aWg, 10*time.Minute) {
logger.Warning(fmt.Sprintf("%s 入库阶段超时 (10分钟),继续统计结果", logPrefix), owner.Email, "team")
}
// 补救后再次检查 Team 是否已满 // 补救后再次检查 Team 是否已满
if isTeamExhausted() { if isTeamExhausted() {

View File

@@ -296,8 +296,27 @@ func (c *CodexAPIAuth) GetSessionID() string {
return c.sessionID return c.sessionID
} }
// ObtainAuthorizationCode 获取授权码 // ObtainAuthorizationCode 获取授权码(全局 3 分钟超时)
func (c *CodexAPIAuth) ObtainAuthorizationCode() (string, error) { func (c *CodexAPIAuth) ObtainAuthorizationCode() (string, error) {
type authResult struct {
code string
err error
}
resultCh := make(chan authResult, 1)
go func() {
code, err := c.obtainAuthorizationCodeInternal()
resultCh <- authResult{code, err}
}()
select {
case r := <-resultCh:
return r.code, r.err
case <-time.After(3 * time.Minute):
return "", fmt.Errorf("授权超时 (3分钟)")
}
}
// obtainAuthorizationCodeInternal ObtainAuthorizationCode 的内部实现
func (c *CodexAPIAuth) obtainAuthorizationCodeInternal() (string, error) {
c.logStep(StepNavigate, "开始 Codex API 授权流程...") c.logStep(StepNavigate, "开始 Codex API 授权流程...")
// 选择使用固定 URL 还是 S2A 生成的 URL // 选择使用固定 URL 还是 S2A 生成的 URL

View File

@@ -94,7 +94,7 @@ func createTLSClient(c *TLSClient, fp BrowserFingerprint, proxyStr string) (*TLS
jar := tls_client.NewCookieJar() jar := tls_client.NewCookieJar()
options := []tls_client.HttpClientOption{ options := []tls_client.HttpClientOption{
tls_client.WithTimeoutSeconds(90), tls_client.WithTimeoutSeconds(45),
tls_client.WithClientProfile(fp.TLSProfile), tls_client.WithClientProfile(fp.TLSProfile),
tls_client.WithRandomTLSExtensionOrder(), tls_client.WithRandomTLSExtensionOrder(),
tls_client.WithCookieJar(jar), tls_client.WithCookieJar(jar),
@@ -131,7 +131,7 @@ func createAzureTLSClient(c *TLSClient, fp BrowserFingerprint, proxyStr string)
session.Browser = browser session.Browser = browser
session.GetClientHelloSpec = azuretls.GetBrowserClientHelloFunc(browser) session.GetClientHelloSpec = azuretls.GetBrowserClientHelloFunc(browser)
session.SetTimeout(90 * time.Second) session.SetTimeout(45 * time.Second)
if proxyStr != "" { if proxyStr != "" {
normalized, err := proxyutil.Normalize(proxyStr) normalized, err := proxyutil.Normalize(proxyStr)

View File

@@ -2,6 +2,7 @@ package mail
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"math/rand" "math/rand"
@@ -355,7 +356,13 @@ func (m *Client) GetEmails(email string, size int) ([]EmailItem, error) {
// WaitForCode 等待验证码邮件 // WaitForCode 等待验证码邮件
func (m *Client) WaitForCode(email string, timeout time.Duration) (string, error) { func (m *Client) WaitForCode(email string, timeout time.Duration) (string, error) {
start := time.Now() ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return m.WaitForCodeWithContext(ctx, email)
}
// WaitForCodeWithContext 等待验证码邮件(支持 context 取消)
func (m *Client) WaitForCodeWithContext(ctx context.Context, email string) (string, error) {
// 匹配6位数字验证码 // 匹配6位数字验证码
codeRegex := regexp.MustCompile(`\b(\d{6})\b`) codeRegex := regexp.MustCompile(`\b(\d{6})\b`)
// 专门匹配 OpenAI 验证码邮件标题格式: "Your ChatGPT code is 016547" 或 "OpenAI - Verify your email" // 专门匹配 OpenAI 验证码邮件标题格式: "Your ChatGPT code is 016547" 或 "OpenAI - Verify your email"
@@ -381,7 +388,12 @@ func (m *Client) WaitForCode(email string, timeout time.Duration) (string, error
} }
} }
for time.Since(start) < timeout { for {
select {
case <-ctx.Done():
return "", fmt.Errorf("验证码获取超时")
default:
}
emails, err := m.GetEmails(email, 10) emails, err := m.GetEmails(email, 10)
if err == nil { if err == nil {
for _, mail := range emails { for _, mail := range emails {
@@ -435,10 +447,12 @@ func (m *Client) WaitForCode(email string, timeout time.Duration) (string, error
} }
} }
} }
time.Sleep(1 * time.Second) select {
case <-ctx.Done():
return "", fmt.Errorf("验证码获取超时")
case <-time.After(1 * time.Second):
}
} }
return "", fmt.Errorf("验证码获取超时")
} }
// WaitForInviteLink 等待邀请邮件并提取链接 // WaitForInviteLink 等待邀请邮件并提取链接
@@ -520,11 +534,22 @@ func GetLatestEmailID(email string) int {
// GetEmailOTPAfterID 获取指定邮件ID之后的OTP验证码 // GetEmailOTPAfterID 获取指定邮件ID之后的OTP验证码
// 基于 get_code.go 的实现,只获取 afterEmailID 之后的新邮件中的验证码 // 基于 get_code.go 的实现,只获取 afterEmailID 之后的新邮件中的验证码
func GetEmailOTPAfterID(email string, afterEmailID int, timeout time.Duration) (string, error) { func GetEmailOTPAfterID(email string, afterEmailID int, timeout time.Duration) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
return GetEmailOTPAfterIDWithContext(ctx, email, afterEmailID)
}
// GetEmailOTPAfterIDWithContext 获取指定邮件ID之后的OTP验证码支持 context 取消)
func GetEmailOTPAfterIDWithContext(ctx context.Context, email string, afterEmailID int) (string, error) {
client := NewClientForEmail(email) client := NewClientForEmail(email)
start := time.Now()
codeRegex := regexp.MustCompile(`\b(\d{6})\b`) codeRegex := regexp.MustCompile(`\b(\d{6})\b`)
for time.Since(start) < timeout { for {
select {
case <-ctx.Done():
return "", fmt.Errorf("验证码获取超时 (afterEmailID=%d)", afterEmailID)
default:
}
emails, err := client.GetEmails(email, 5) emails, err := client.GetEmails(email, 5)
if err == nil { if err == nil {
for _, mail := range emails { for _, mail := range emails {
@@ -554,8 +579,10 @@ func GetEmailOTPAfterID(email string, afterEmailID int, timeout time.Duration) (
} }
} }
} }
time.Sleep(1 * time.Second) select {
case <-ctx.Done():
return "", fmt.Errorf("验证码获取超时 (afterEmailID=%d)", afterEmailID)
case <-time.After(1 * time.Second):
}
} }
return "", fmt.Errorf("验证码获取超时 (afterEmailID=%d)", afterEmailID)
} }

View File

@@ -281,8 +281,27 @@ func Run(email, password, realName, birthdate, proxy string) (*ChatGPTReg, error
return APIRegister(email, password, realName, birthdate, proxy, "[RegTest]") return APIRegister(email, password, realName, birthdate, proxy, "[RegTest]")
} }
// APIRegister 使用 API 完成注册 (集成 403 重试机制) // APIRegister 使用 API 完成注册 (集成 403 重试机制,全局 5 分钟超时)
func APIRegister(email, password, realName, birthdate, proxy string, logPrefix string) (*ChatGPTReg, error) { func APIRegister(email, password, realName, birthdate, proxy string, logPrefix string) (*ChatGPTReg, error) {
type regResult struct {
reg *ChatGPTReg
err error
}
resultCh := make(chan regResult, 1)
go func() {
reg, err := apiRegisterInternal(email, password, realName, birthdate, proxy, logPrefix)
resultCh <- regResult{reg, err}
}()
select {
case r := <-resultCh:
return r.reg, r.err
case <-time.After(5 * time.Minute):
return nil, fmt.Errorf("注册超时 (5分钟)")
}
}
// apiRegisterInternal APIRegister 的内部实现
func apiRegisterInternal(email, password, realName, birthdate, proxy string, logPrefix string) (*ChatGPTReg, error) {
var reg *ChatGPTReg var reg *ChatGPTReg
var lastErr error var lastErr error