package main import ( "encoding/json" "fmt" "os" "strings" "sync" "sync/atomic" "time" "codex-pool/internal/auth" "codex-pool/internal/config" "codex-pool/internal/invite" "codex-pool/internal/mail" "codex-pool/internal/register" ) type Account struct { Account string `json:"account"` Password string `json:"password"` Token string `json:"token"` } type MemberAccount struct { Email string Password string Success bool } const ( MembersPerTeam = 4 // 每个 team 注册的成员数 NumTeams = 2 // 并发运行的 team 数量 ) // ANSI 颜色码 const ( ColorReset = "\033[0m" ColorRed = "\033[31m" ColorGreen = "\033[32m" ColorYellow = "\033[33m" ColorBlue = "\033[34m" ColorMagenta = "\033[35m" ColorCyan = "\033[36m" ColorWhite = "\033[37m" ColorBold = "\033[1m" ) // Team 颜色 var teamColors = []string{ ColorCyan, // Team 1 ColorMagenta, // Team 2 ColorYellow, // Team 3 ColorBlue, // Team 4 } // TeamLogger 带颜色的Team日志 type TeamLogger struct { prefix string color string mu sync.Mutex } func NewTeamLogger(teamIdx int) *TeamLogger { color := teamColors[teamIdx%len(teamColors)] return &TeamLogger{ prefix: fmt.Sprintf("[Team %d]", teamIdx+1), color: color, } } func (l *TeamLogger) Log(format string, args ...interface{}) { l.mu.Lock() defer l.mu.Unlock() msg := fmt.Sprintf(format, args...) fmt.Printf("%s%s%s %s\n", l.color, l.prefix, ColorReset, msg) } func (l *TeamLogger) Success(format string, args ...interface{}) { l.mu.Lock() defer l.mu.Unlock() msg := fmt.Sprintf(format, args...) fmt.Printf("%s%s%s %s✓%s %s\n", l.color, l.prefix, ColorReset, ColorGreen, ColorReset, msg) } func (l *TeamLogger) Error(format string, args ...interface{}) { l.mu.Lock() defer l.mu.Unlock() msg := fmt.Sprintf(format, args...) fmt.Printf("%s%s%s %s✗%s %s\n", l.color, l.prefix, ColorReset, ColorRed, ColorReset, msg) } func (l *TeamLogger) Info(format string, args ...interface{}) { l.mu.Lock() defer l.mu.Unlock() msg := fmt.Sprintf(format, args...) fmt.Printf("%s%s%s %s→%s %s\n", l.color, l.prefix, ColorReset, ColorYellow, ColorReset, msg) } // Highlight 整行绿色高亮(用于重要成功信息) func (l *TeamLogger) Highlight(format string, args ...interface{}) { l.mu.Lock() defer l.mu.Unlock() msg := fmt.Sprintf(format, args...) fmt.Printf("%s%s %s✓ %s%s\n", ColorGreen, l.prefix, ColorBold, msg, ColorReset) } func main() { fmt.Printf("%s%s=================================================================%s\n", ColorBold, ColorWhite, ColorReset) fmt.Printf("%s Multi-Team Concurrent Test (Chromedp)%s\n", ColorBold, ColorReset) fmt.Printf(" - %d Teams running concurrently\n", NumTeams) fmt.Printf(" - %d Members per team\n", MembersPerTeam) fmt.Printf("%s%s=================================================================%s\n", ColorBold, ColorWhite, ColorReset) fmt.Println() // 加载配置 configPath := config.FindPath() cfg, err := config.Load(configPath) if err != nil { fmt.Printf("%s[Error]%s Failed to load config: %v\n", ColorRed, ColorReset, err) os.Exit(1) } // 初始化邮箱服务 if len(cfg.MailServices) > 0 { mail.Init(cfg.MailServices) } // 加载账号 accountsFile := "accounts-3-20260130-052841.json" data, err := os.ReadFile(accountsFile) if err != nil { fmt.Printf("%s[Error]%s Failed to read accounts file: %v\n", ColorRed, ColorReset, err) os.Exit(1) } var accounts []Account if err := json.Unmarshal(data, &accounts); err != nil { fmt.Printf("%s[Error]%s Failed to parse accounts file: %v\n", ColorRed, ColorReset, err) os.Exit(1) } if len(accounts) < NumTeams { fmt.Printf("%s[Error]%s Need at least %d owner accounts, got %d\n", ColorRed, ColorReset, NumTeams, len(accounts)) os.Exit(1) } proxy := cfg.DefaultProxy if proxy == "" { proxy = "http://127.0.0.1:7890" } fmt.Printf("[Proxy] %s\n", proxy) fmt.Println() // 显示 Owner 列表 fmt.Printf("%s========================================%s\n", ColorBold, ColorReset) fmt.Printf("%s[Owners]%s\n", ColorBold, ColorReset) fmt.Printf("%s========================================%s\n", ColorBold, ColorReset) for i := 0; i < NumTeams; i++ { color := teamColors[i%len(teamColors)] fmt.Printf(" %sTeam %d:%s %s\n", color, i+1, ColorReset, accounts[i].Account) } fmt.Println() // 并发运行多个 Team var wg sync.WaitGroup var totalRegistered int32 var totalS2A int32 startTime := time.Now() for teamIdx := 0; teamIdx < NumTeams; teamIdx++ { wg.Add(1) go func(idx int) { defer wg.Done() registered, s2a := runTeam(idx, accounts[idx], cfg, proxy) atomic.AddInt32(&totalRegistered, int32(registered)) atomic.AddInt32(&totalS2A, int32(s2a)) }(teamIdx) } wg.Wait() totalDuration := time.Since(startTime) // 总结 fmt.Println() fmt.Printf("%s%s=================================================================%s\n", ColorBold, ColorWhite, ColorReset) fmt.Printf("%s All Teams Complete%s\n", ColorBold, ColorReset) fmt.Printf("%s=================================================================%s\n", ColorBold, ColorReset) fmt.Printf(" Total Registered: %s%d/%d%s\n", ColorGreen, totalRegistered, NumTeams*MembersPerTeam, ColorReset) fmt.Printf(" Total Added to S2A: %s%d%s\n", ColorGreen, totalS2A, ColorReset) fmt.Printf(" Total Duration: %v\n", totalDuration) fmt.Printf("%s=================================================================%s\n", ColorBold, ColorReset) } // runTeam 运行单个 Team 的流程 func runTeam(teamIdx int, owner Account, cfg *config.Config, proxy string) (registered, s2a int) { log := NewTeamLogger(teamIdx) log.Log("Starting with owner: %s", owner.Account) // Step 1: 获取 Team ID log.Info("Fetching Team ID...") inviter := invite.NewWithProxy(owner.Token, proxy) teamID, err := inviter.GetAccountID() if err != nil { log.Error("Failed to get Team ID: %v", err) return 0, 0 } log.Success("Team ID: %s", teamID) // Step 2: 生成成员邮箱 log.Info("Generating %d member emails...", MembersPerTeam) children := make([]MemberAccount, MembersPerTeam) for i := 0; i < MembersPerTeam; i++ { children[i].Email = mail.GenerateEmail() children[i].Password = register.GeneratePassword() log.Log("[Member %d] Email: %s", i+1, children[i].Email) } // 批量发送邀请 log.Info("Sending invites...") inviteEmails := make([]string, MembersPerTeam) for i, c := range children { inviteEmails[i] = c.Email } if err := inviter.SendInvites(inviteEmails); err != nil { log.Error("Failed to send invites: %v", err) return 0, 0 } log.Success("Sent %d invite(s)", len(inviteEmails)) // Step 3: 并发注册成员 log.Info("Starting member registration...") var memberWg sync.WaitGroup var successCount int32 memberMutex := sync.Mutex{} for i := range children { memberWg.Add(1) go func(memberIdx int) { defer memberWg.Done() memberMutex.Lock() email := children[memberIdx].Email password := children[memberIdx].Password memberMutex.Unlock() name := register.GenerateName() birthdate := register.GenerateBirthdate() // 最多重试3次 for attempt := 0; attempt < 3; attempt++ { if attempt > 0 { email = mail.GenerateEmail() password = register.GeneratePassword() log.Log("[Member %d] Retry %d - New email: %s", memberIdx+1, attempt, email) // 发送新邀请 if err := inviter.SendInvites([]string{email}); err != nil { log.Error("[Member %d] Failed to send retry invite: %v", memberIdx+1, err) continue } log.Success("[Member %d] Sent retry invite", memberIdx+1) } // 详细注册流程 if err := registerMemberDetailed(log, memberIdx+1, email, password, name, birthdate, proxy); err != nil { if strings.Contains(err.Error(), "验证码") { log.Error("[Member %d] OTP timeout, will retry...", memberIdx+1) continue } log.Error("[Member %d] Registration failed: %v", memberIdx+1, err) return } // 成功 memberMutex.Lock() children[memberIdx].Email = email children[memberIdx].Password = password children[memberIdx].Success = true memberMutex.Unlock() atomic.AddInt32(&successCount, 1) log.Success("[Member %d] Registration complete!", memberIdx+1) return } log.Error("[Member %d] Failed after 3 retries", memberIdx+1) }(i) } memberWg.Wait() registered = int(successCount) log.Success("Registration phase complete: %d/%d", registered, MembersPerTeam) // 收集成功的成员 var registeredChildren []MemberAccount for _, c := range children { if c.Success { registeredChildren = append(registeredChildren, c) } } if len(registeredChildren) == 0 { log.Error("No members registered") return registered, 0 } // Step 4: 串行入库 log.Info("Starting S2A authorization...") for i, child := range registeredChildren { log.Log("[Member %d] Getting S2A auth URL...", i+1) s2aResp, err := auth.GenerateS2AAuthURL(cfg.S2AApiBase, cfg.S2AAdminKey, cfg.ProxyID) if err != nil { log.Error("[Member %d] Auth URL failed: %v", i+1, err) continue } log.Success("[Member %d] Got auth URL", i+1) log.Log("[Member %d] Running browser automation (Chromedp)...", i+1) code, err := auth.CompleteWithChromedp(s2aResp.Data.AuthURL, child.Email, child.Password, teamID, true, proxy) if err != nil { log.Error("[Member %d] Browser auth failed: %v", i+1, err) continue } log.Success("[Member %d] Browser auth complete", i+1) log.Log("[Member %d] Submitting to S2A...", i+1) result, err := auth.SubmitS2AOAuth( cfg.S2AApiBase, cfg.S2AAdminKey, s2aResp.Data.SessionID, code, child.Email, cfg.Concurrency, cfg.Priority, cfg.GroupIDs, cfg.ProxyID, ) if err != nil { log.Error("[Member %d] S2A submit failed: %v", i+1, err) continue } log.Highlight("[Member %d] Added to S2A! ID=%d, Status=%s", i+1, result.Data.ID, result.Data.Status) s2a++ time.Sleep(500 * time.Millisecond) } log.Success("Team complete: %d registered, %d in S2A", registered, s2a) return registered, s2a } // registerMemberDetailed 详细的注册流程,带日志 func registerMemberDetailed(log *TeamLogger, memberNum int, email, password, name, birthdate, proxy string) error { prefix := fmt.Sprintf("[Member %d]", memberNum) log.Log("%s Creating TLS client...", prefix) reg, err := register.New(proxy) if err != nil { return err } log.Log("%s Initializing session...", prefix) if err := reg.InitSession(); err != nil { return fmt.Errorf("初始化失败: %v", err) } log.Success("%s Session initialized", prefix) log.Log("%s Getting authorize URL...", prefix) if err := reg.GetAuthorizeURL(email); err != nil { return fmt.Errorf("获取授权URL失败: %v", err) } log.Success("%s Got authorize URL", prefix) log.Log("%s Starting authorize flow...", prefix) if err := reg.StartAuthorize(); err != nil { return fmt.Errorf("启动授权失败: %v", err) } log.Success("%s Authorize flow started", prefix) log.Log("%s Registering account...", prefix) if err := reg.Register(email, password); err != nil { return fmt.Errorf("注册失败: %v", err) } log.Success("%s Account registered", prefix) log.Log("%s Sending verification email...", prefix) if err := reg.SendVerificationEmail(); err != nil { return fmt.Errorf("发送邮件失败: %v", err) } log.Success("%s Verification email sent", prefix) log.Log("%s Waiting for OTP code (5s timeout)...", prefix) otpCode, err := mail.GetVerificationCode(email, 5*time.Second) if err != nil { log.Log("%s OTP not received in 5s, waiting 15s more...", prefix) otpCode, err = mail.GetVerificationCode(email, 15*time.Second) if err != nil { return fmt.Errorf("验证码获取超时") } } log.Success("%s Got OTP: %s", prefix, otpCode) log.Log("%s Validating OTP...", prefix) if err := reg.ValidateOTP(otpCode); err != nil { return fmt.Errorf("OTP验证失败: %v", err) } log.Success("%s OTP validated", prefix) log.Log("%s Creating account (name=%s, birthdate=%s)...", prefix, name, birthdate) if err := reg.CreateAccount(name, birthdate); err != nil { return fmt.Errorf("创建账户失败: %v", err) } log.Success("%s Account created", prefix) log.Log("%s Getting session token...", prefix) _ = reg.GetSessionToken() if reg.AccessToken != "" { log.Success("%s Got access token: %s...", prefix, truncate(reg.AccessToken, 30)) } return nil } func truncate(s string, maxLen int) string { if len(s) <= maxLen { return s } return s[:maxLen] }