package chatgpt import ( "bytes" "encoding/json" "fmt" "io" "log" "math" "net/http" "strings" "time" "go-helper/internal/model" ) const ( maxInviteAttempts = 3 retryBaseDelay = 800 * time.Millisecond retryMaxDelay = 5 * time.Second ) // InviteUser sends an invitation to the given email on the specified team account. func (c *Client) InviteUser(email string, account *model.GptAccount) error { email = strings.TrimSpace(strings.ToLower(email)) if email == "" { return fmt.Errorf("缺少邀请邮箱") } if account.Token == "" || account.ChatgptAccountID == "" { return fmt.Errorf("账号配置不完整") } apiURL := fmt.Sprintf("https://chatgpt.com/backend-api/accounts/%s/invites", account.ChatgptAccountID) payload := map[string]interface{}{ "email_addresses": []string{email}, "role": "standard-user", "resend_emails": true, } bodyBytes, _ := json.Marshal(payload) var lastErr error for attempt := 1; attempt <= maxInviteAttempts; attempt++ { req, err := http.NewRequest("POST", apiURL, bytes.NewReader(bodyBytes)) if err != nil { return err } headers := buildHeaders(account.Token, account.ChatgptAccountID) req.Header = headers req.Header.Set("Content-Type", "application/json") if account.OaiDeviceID != "" { req.Header.Set("Oai-Device-Id", account.OaiDeviceID) } resp, err := c.httpClient.Do(req) if err != nil { lastErr = fmt.Errorf("网络错误: %w", err) log.Printf("[Invite] 尝试 %d/%d 网络错误: %v", attempt, maxInviteAttempts, err) if attempt < maxInviteAttempts { sleepRetry(attempt) } continue } respBody, _ := io.ReadAll(resp.Body) resp.Body.Close() if resp.StatusCode >= 200 && resp.StatusCode < 300 { log.Printf("[Invite] 成功邀请 %s 到账号 %s", email, account.ChatgptAccountID) return nil } lastErr = fmt.Errorf("HTTP %d: %s", resp.StatusCode, truncate(string(respBody), 300)) log.Printf("[Invite] 尝试 %d/%d 失败: %v", attempt, maxInviteAttempts, lastErr) if !isRetryableStatus(resp.StatusCode) || attempt >= maxInviteAttempts { break } sleepRetry(attempt) } return fmt.Errorf("邀请失败(已尝试 %d 次): %v", maxInviteAttempts, lastErr) } func sleepRetry(attempt int) { delay := float64(retryBaseDelay) * math.Pow(2, float64(attempt-1)) if delay > float64(retryMaxDelay) { delay = float64(retryMaxDelay) } time.Sleep(time.Duration(delay)) } func isRetryableStatus(status int) bool { return status == 408 || status == 429 || (status >= 500 && status <= 599) }