preparegroup.go 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. package telegram
  2. import (
  3. "context"
  4. "log"
  5. "time"
  6. )
  7. // PrepareAccountResult reports the outcome of one account's prepare attempt.
  8. type PrepareAccountResult struct {
  9. Phone string `json:"phone"`
  10. Joined bool `json:"joined"` // true if newly joined OR USER_ALREADY_PARTICIPANT
  11. Message string `json:"message"` // error message or "已加入" / "首次加入成功"
  12. }
  13. // PrepareGroup has every currently-enabled account attempt to join the given
  14. // group/channel. No scraping is performed — this is a batch "warm up" so that
  15. // subsequent clone passes have accounts that TG treats as real group members.
  16. // Best practice is to run this, then wait hours/days, then scrape.
  17. //
  18. // Accounts that hit FloodWait are cooled via HandleFloodWait just like in
  19. // other flows.
  20. func PrepareGroup(ctx context.Context, mgr *AccountManager, username string) ([]PrepareAccountResult, error) {
  21. // Acquire every available account in sequence (Acquire blocks only on mu).
  22. // If an account is cooling, it's skipped with a "cooling" result.
  23. statuses := mgr.GetStatuses()
  24. results := make([]PrepareAccountResult, 0, len(statuses))
  25. for phone, status := range statuses {
  26. if status == "cooling" {
  27. results = append(results, PrepareAccountResult{Phone: phone, Joined: false, Message: "冷却中,跳过"})
  28. continue
  29. }
  30. acc, err := mgr.Acquire(ctx)
  31. if err != nil {
  32. results = append(results, PrepareAccountResult{Phone: phone, Joined: false, Message: err.Error()})
  33. // ErrAllCooling → no more accounts to try
  34. break
  35. }
  36. // Acquire returns SOME available account — might not be the one we were
  37. // iterating on. Use acc.Account.Phone for reporting. The phone variable
  38. // from the outer loop is only used to skip cooling ones.
  39. pr := PrepareAccountResult{Phone: acc.Account.Phone}
  40. connectCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
  41. if err := acc.Client.Connect(connectCtx); err != nil {
  42. cancel()
  43. pr.Message = "connect: " + err.Error()
  44. mgr.Release(acc, 0)
  45. results = append(results, pr)
  46. continue
  47. }
  48. inputCh, ch, _, err := acc.Client.ResolveGroupPeer(connectCtx, username)
  49. if err != nil {
  50. cancel()
  51. pr.Message = "resolve: " + err.Error()
  52. acc.Client.Disconnect()
  53. mgr.Release(acc, 0)
  54. results = append(results, pr)
  55. continue
  56. }
  57. if inputCh == nil {
  58. cancel()
  59. pr.Message = "非超级群组,基础聊天无需加群"
  60. pr.Joined = true
  61. acc.Client.Disconnect()
  62. mgr.Release(acc, 0)
  63. results = append(results, pr)
  64. continue
  65. }
  66. // JoinChannel returns nil for both success and USER_ALREADY_PARTICIPANT.
  67. joinErr := acc.Client.JoinChannel(connectCtx, inputCh)
  68. cancel()
  69. if joinErr != nil {
  70. if fwe, ok := joinErr.(*FloodWaitError); ok {
  71. pr.Message = "FloodWait: " + joinErr.Error()
  72. mgr.HandleFloodWait(acc, fwe.Seconds)
  73. } else {
  74. pr.Message = joinErr.Error()
  75. mgr.Release(acc, 0)
  76. }
  77. acc.Client.Disconnect()
  78. results = append(results, pr)
  79. continue
  80. }
  81. pr.Joined = true
  82. title := ""
  83. if ch != nil {
  84. title = ch.Title
  85. }
  86. pr.Message = "加入成功 " + title
  87. log.Printf("[prepare_group] %s joined %s via %s", username, title, acc.Account.Phone)
  88. acc.Client.Disconnect()
  89. mgr.Release(acc, 0)
  90. results = append(results, pr)
  91. }
  92. return results, nil
  93. }