package telegram import ( "context" "log" "time" ) // PrepareAccountResult reports the outcome of one account's prepare attempt. type PrepareAccountResult struct { Phone string `json:"phone"` Joined bool `json:"joined"` // true if newly joined OR USER_ALREADY_PARTICIPANT Message string `json:"message"` // error message or "已加入" / "首次加入成功" } // PrepareGroup has every currently-enabled account attempt to join the given // group/channel. No scraping is performed — this is a batch "warm up" so that // subsequent clone passes have accounts that TG treats as real group members. // Best practice is to run this, then wait hours/days, then scrape. // // Accounts that hit FloodWait are cooled via HandleFloodWait just like in // other flows. func PrepareGroup(ctx context.Context, mgr *AccountManager, username string) ([]PrepareAccountResult, error) { // Acquire every available account in sequence (Acquire blocks only on mu). // If an account is cooling, it's skipped with a "cooling" result. statuses := mgr.GetStatuses() results := make([]PrepareAccountResult, 0, len(statuses)) for phone, status := range statuses { if status == "cooling" { results = append(results, PrepareAccountResult{Phone: phone, Joined: false, Message: "冷却中,跳过"}) continue } acc, err := mgr.Acquire(ctx) if err != nil { results = append(results, PrepareAccountResult{Phone: phone, Joined: false, Message: err.Error()}) // ErrAllCooling → no more accounts to try break } // Acquire returns SOME available account — might not be the one we were // iterating on. Use acc.Account.Phone for reporting. The phone variable // from the outer loop is only used to skip cooling ones. pr := PrepareAccountResult{Phone: acc.Account.Phone} connectCtx, cancel := context.WithTimeout(ctx, 30*time.Second) if err := acc.Client.Connect(connectCtx); err != nil { cancel() pr.Message = "connect: " + err.Error() mgr.Release(acc, 0) results = append(results, pr) continue } inputCh, ch, _, err := acc.Client.ResolveGroupPeer(connectCtx, username) if err != nil { cancel() pr.Message = "resolve: " + err.Error() acc.Client.Disconnect() mgr.Release(acc, 0) results = append(results, pr) continue } if inputCh == nil { cancel() pr.Message = "非超级群组,基础聊天无需加群" pr.Joined = true acc.Client.Disconnect() mgr.Release(acc, 0) results = append(results, pr) continue } // JoinChannel returns nil for both success and USER_ALREADY_PARTICIPANT. joinErr := acc.Client.JoinChannel(connectCtx, inputCh) cancel() if joinErr != nil { if fwe, ok := joinErr.(*FloodWaitError); ok { pr.Message = "FloodWait: " + joinErr.Error() mgr.HandleFloodWait(acc, fwe.Seconds) } else { pr.Message = joinErr.Error() mgr.Release(acc, 0) } acc.Client.Disconnect() results = append(results, pr) continue } pr.Joined = true title := "" if ch != nil { title = ch.Title } pr.Message = "加入成功 " + title log.Printf("[prepare_group] %s joined %s via %s", username, title, acc.Account.Phone) acc.Client.Disconnect() mgr.Release(acc, 0) results = append(results, pr) } return results, nil }