feat: Add automated ChatGPT account registration with backend API, TLS client, and fingerprinting, alongside new frontend pages for configuration, monitoring, and upload.

This commit is contained in:
2026-02-03 02:39:08 +08:00
parent 389b8eca28
commit 51ba54856d
18 changed files with 1382 additions and 674 deletions

View File

@@ -66,6 +66,25 @@ type TeamProcessState struct {
var teamProcessState = &TeamProcessState{}
// getProxyDisplay 获取代理显示名称(隐藏密码)
func getProxyDisplay(proxy string) string {
if proxy == "" {
return "无代理"
}
// 尝试解析 URL只返回 host 部分
if strings.Contains(proxy, "@") {
parts := strings.Split(proxy, "@")
if len(parts) >= 2 {
return parts[len(parts)-1] // 返回 @ 后面的 host:port 部分
}
}
// 去掉协议前缀
proxy = strings.TrimPrefix(proxy, "http://")
proxy = strings.TrimPrefix(proxy, "https://")
proxy = strings.TrimPrefix(proxy, "socks5://")
return proxy
}
// HandleTeamProcess POST /api/team/process - 启动 Team 批量处理
func HandleTeamProcess(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
@@ -593,7 +612,7 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
}
// 注册
_, err := registerWithTimeout(currentEmail, currentPassword, name, birthdate, req.Proxy)
_, err := register.APIRegister(currentEmail, currentPassword, name, birthdate, req.Proxy, memberLogPrefix)
if err != nil {
logger.Error(fmt.Sprintf("%s [注册失败] %v", memberLogPrefix, err), currentEmail, "team")
continue
@@ -753,15 +772,23 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
continue
}
// 根据配置选择浏览器自动化
// 根据配置选择授权方式
var code string
// 根据全局配置决定授权方式
if config.Global.AuthMethod == "api" {
// 使用纯 API 模式CodexAuth- 使用 S2A 生成的授权 URL
code, err = auth.CompleteWithCodexAPI(memberChild.Email, memberChild.Password, teamID, s2aResp.Data.AuthURL, s2aResp.Data.SessionID, req.Proxy, authLogger)
} else if req.BrowserType == "rod" {
code, err = auth.CompleteWithRodLogged(s2aResp.Data.AuthURL, memberChild.Email, memberChild.Password, teamID, req.Headless, req.Proxy, authLogger)
// 从代理池随机选择代理
proxyToUse := req.Proxy
if poolProxy, poolErr := database.Instance.GetRandomCodexProxy(); poolErr == nil && poolProxy != "" {
proxyToUse = poolProxy
logger.Info(fmt.Sprintf("%s 使用代理池: %s", memberLogPrefix, getProxyDisplay(poolProxy)), memberChild.Email, "team")
}
code, err = auth.CompleteWithCodexAPI(memberChild.Email, memberChild.Password, teamID, s2aResp.Data.AuthURL, s2aResp.Data.SessionID, proxyToUse, authLogger)
// 更新代理统计
if proxyToUse != req.Proxy && proxyToUse != "" {
database.Instance.UpdateCodexProxyStats(proxyToUse, err == nil)
}
} else {
// 使用 Chromedp 浏览器自动化
code, err = auth.CompleteWithChromedpLogged(s2aResp.Data.AuthURL, memberChild.Email, memberChild.Password, teamID, req.Headless, req.Proxy, authLogger)
}
if err != nil {
@@ -858,10 +885,19 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
// 根据全局配置决定授权方式
if config.Global.AuthMethod == "api" {
// 使用纯 API 模式CodexAuth- 使用 S2A 生成的授权 URL
code, err = auth.CompleteWithCodexAPI(owner.Email, owner.Password, teamID, s2aResp.Data.AuthURL, s2aResp.Data.SessionID, req.Proxy, authLogger)
} else if req.BrowserType == "rod" {
code, err = auth.CompleteWithRodLogged(s2aResp.Data.AuthURL, owner.Email, owner.Password, teamID, req.Headless, req.Proxy, authLogger)
// 从代理池随机选择代理
proxyToUse := req.Proxy
if poolProxy, poolErr := database.Instance.GetRandomCodexProxy(); poolErr == nil && poolProxy != "" {
proxyToUse = poolProxy
logger.Info(fmt.Sprintf("%s 使用代理池: %s", ownerLogPrefix, getProxyDisplay(poolProxy)), owner.Email, "team")
}
code, err = auth.CompleteWithCodexAPI(owner.Email, owner.Password, teamID, s2aResp.Data.AuthURL, s2aResp.Data.SessionID, proxyToUse, authLogger)
// 更新代理统计
if proxyToUse != req.Proxy && proxyToUse != "" {
database.Instance.UpdateCodexProxyStats(proxyToUse, err == nil)
}
} else {
// 使用 Chromedp 浏览器自动化
code, err = auth.CompleteWithChromedpLogged(s2aResp.Data.AuthURL, owner.Email, owner.Password, teamID, req.Headless, req.Proxy, authLogger)
}
if err != nil {
@@ -909,81 +945,3 @@ func processSingleTeam(idx int, req TeamProcessRequest) (result TeamProcessResul
return result
}
// registerWithTimeout 带超时的注册(遇到 403 会换指纹重试)
func registerWithTimeout(email, password, name, birthdate, proxy string) (*register.ChatGPTReg, error) {
const maxInitRetries = 3
var reg *register.ChatGPTReg
var initErr error
// 初始化阶段:遇到 403 换指纹重试
for attempt := 0; attempt < maxInitRetries; attempt++ {
var err error
reg, err = register.New(proxy)
if err != nil {
return nil, err
}
if err := reg.InitSession(); err != nil {
initErr = err
// 检查是否是 403 错误,换指纹重试
if strings.Contains(err.Error(), "403") {
continue
}
return nil, fmt.Errorf("初始化失败: %v", err)
}
if err := reg.GetAuthorizeURL(email); err != nil {
// 403 也可能在这里出现
if strings.Contains(err.Error(), "403") {
initErr = err
continue
}
return nil, fmt.Errorf("获取授权URL失败: %v", err)
}
if err := reg.StartAuthorize(); err != nil {
if strings.Contains(err.Error(), "403") {
initErr = err
continue
}
return nil, fmt.Errorf("启动授权失败: %v", err)
}
// 初始化成功,跳出重试循环
initErr = nil
break
}
if initErr != nil {
return nil, fmt.Errorf("初始化失败(重试%d次): %v", maxInitRetries, initErr)
}
if err := reg.Register(email, password); err != nil {
return nil, fmt.Errorf("注册失败: %v", err)
}
if err := reg.SendVerificationEmail(); err != nil {
return nil, fmt.Errorf("发送邮件失败: %v", err)
}
// 短超时获取验证码
otpCode, err := mail.GetVerificationCode(email, 5*time.Second)
if err != nil {
otpCode, err = mail.GetVerificationCode(email, 15*time.Second)
if err != nil {
return nil, fmt.Errorf("验证码获取超时")
}
}
if err := reg.ValidateOTP(otpCode); err != nil {
return nil, fmt.Errorf("OTP验证失败: %v", err)
}
if err := reg.CreateAccount(name, birthdate); err != nil {
return nil, fmt.Errorf("创建账户失败: %v", err)
}
_ = reg.GetSessionToken()
return reg, nil
}