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:
@@ -67,6 +67,18 @@ type TeamProcessState struct {
|
||||
|
||||
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 获取代理显示名称(隐藏密码)
|
||||
func getProxyDisplay(proxy string) string {
|
||||
if proxy == "" {
|
||||
@@ -648,8 +660,17 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
|
||||
memberLogPrefix := fmt.Sprintf("%s [Member %d]", logPrefix, memberIdx+1)
|
||||
memberStartTime := time.Now()
|
||||
|
||||
// 获取入库信号量
|
||||
s2aSem <- struct{}{}
|
||||
// 获取入库信号量(3分钟超时)
|
||||
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 }()
|
||||
|
||||
// 从代理池获取随机代理(默认轮询使用代理池,无代理则直连)
|
||||
@@ -764,23 +785,29 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
|
||||
currentEmail := email
|
||||
currentPassword := password
|
||||
if attempt > 0 {
|
||||
// 重试时使用新邮箱
|
||||
currentEmail = mail.GenerateEmail()
|
||||
currentPassword = register.GeneratePassword()
|
||||
logger.Warning(fmt.Sprintf("%s 重试 (第%d次), 新邮箱: %s", memberLogPrefix, attempt+1, currentEmail), currentEmail, "team")
|
||||
// 注册失败重试:保持原邮箱(邀请已发送),仅重新注册
|
||||
logger.Warning(fmt.Sprintf("%s 注册重试 (第%d次), 保持邮箱: %s", memberLogPrefix, attempt+1, currentEmail), currentEmail, "team")
|
||||
}
|
||||
|
||||
// 发送邀请
|
||||
if err := inviter.SendInvites([]string{currentEmail}); err != nil {
|
||||
errStr := err.Error()
|
||||
logger.Error(fmt.Sprintf("%s 邀请失败: %v", memberLogPrefix, err), currentEmail, "team")
|
||||
// 首次尝试时发送邀请,重试时跳过(邀请已发送到该邮箱)
|
||||
if attempt == 0 {
|
||||
if err := inviter.SendInvites([]string{currentEmail}); err != nil {
|
||||
errStr := err.Error()
|
||||
logger.Error(fmt.Sprintf("%s 邀请失败: %v", memberLogPrefix, err), currentEmail, "team")
|
||||
|
||||
// 检测 Team 已达邀请上限(401 或 maximum number of seats)
|
||||
if strings.Contains(errStr, "401") || strings.Contains(errStr, "maximum number of seats") {
|
||||
markTeamExhausted()
|
||||
return false
|
||||
// 检测 Team 已达邀请上限(401 或 maximum number of seats)
|
||||
if strings.Contains(errStr, "401") || strings.Contains(errStr, "maximum number of seats") {
|
||||
markTeamExhausted()
|
||||
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 可能已标记)
|
||||
@@ -841,11 +868,15 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
|
||||
registerAndS2AMember(idx, email, password)
|
||||
}(i)
|
||||
}
|
||||
regWg.Wait()
|
||||
if !waitGroupWithTimeout(®Wg, 8*time.Minute) {
|
||||
logger.Warning(fmt.Sprintf("%s 注册阶段超时 (8分钟),继续处理已完成的成员", logPrefix), owner.Email, "team")
|
||||
}
|
||||
|
||||
// 如果 Team 已满,等待已启动的入库完成
|
||||
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.Errors = append(result.Errors, "Team 邀请已满")
|
||||
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 是否已满
|
||||
if isTeamExhausted() {
|
||||
|
||||
@@ -296,8 +296,27 @@ func (c *CodexAPIAuth) GetSessionID() string {
|
||||
return c.sessionID
|
||||
}
|
||||
|
||||
// ObtainAuthorizationCode 获取授权码
|
||||
// ObtainAuthorizationCode 获取授权码(全局 3 分钟超时)
|
||||
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 授权流程...")
|
||||
|
||||
// 选择使用固定 URL 还是 S2A 生成的 URL
|
||||
|
||||
@@ -94,7 +94,7 @@ func createTLSClient(c *TLSClient, fp BrowserFingerprint, proxyStr string) (*TLS
|
||||
jar := tls_client.NewCookieJar()
|
||||
|
||||
options := []tls_client.HttpClientOption{
|
||||
tls_client.WithTimeoutSeconds(90),
|
||||
tls_client.WithTimeoutSeconds(45),
|
||||
tls_client.WithClientProfile(fp.TLSProfile),
|
||||
tls_client.WithRandomTLSExtensionOrder(),
|
||||
tls_client.WithCookieJar(jar),
|
||||
@@ -131,7 +131,7 @@ func createAzureTLSClient(c *TLSClient, fp BrowserFingerprint, proxyStr string)
|
||||
|
||||
session.Browser = browser
|
||||
session.GetClientHelloSpec = azuretls.GetBrowserClientHelloFunc(browser)
|
||||
session.SetTimeout(90 * time.Second)
|
||||
session.SetTimeout(45 * time.Second)
|
||||
|
||||
if proxyStr != "" {
|
||||
normalized, err := proxyutil.Normalize(proxyStr)
|
||||
|
||||
@@ -2,6 +2,7 @@ package mail
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
@@ -355,7 +356,13 @@ func (m *Client) GetEmails(email string, size int) ([]EmailItem, error) {
|
||||
|
||||
// WaitForCode 等待验证码邮件
|
||||
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位数字验证码
|
||||
codeRegex := regexp.MustCompile(`\b(\d{6})\b`)
|
||||
// 专门匹配 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)
|
||||
if err == nil {
|
||||
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 等待邀请邮件并提取链接
|
||||
@@ -520,11 +534,22 @@ func GetLatestEmailID(email string) int {
|
||||
// GetEmailOTPAfterID 获取指定邮件ID之后的OTP验证码
|
||||
// 基于 get_code.go 的实现,只获取 afterEmailID 之后的新邮件中的验证码
|
||||
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)
|
||||
start := time.Now()
|
||||
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)
|
||||
if err == nil {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -281,8 +281,27 @@ func Run(email, password, realName, birthdate, proxy string) (*ChatGPTReg, error
|
||||
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) {
|
||||
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 lastErr error
|
||||
|
||||
|
||||
Reference in New Issue
Block a user