feat: 初始化 ChatGPT Team 管理机器人
核心功能: - 实现基于 Telegram Inline Button 交互的后台面板与用户端 - 支持通过账密登录和 RT (Refresh Token) 方式添加 ChatGPT Team 账号 - 支持管理、拉取和删除待处理邀请,支持一键清空多余邀请 - 支持按剩余容量自动生成邀请兑换码,支持分页查看与一键清空未使用兑换码 - 随机邀请功能:成功拉人后自动核销兑换码 - 定时检测 Token 状态,实现自动续订/刷新并拦截封禁账号 (处理 401/402 错误) 系统与配置: - 使用 PostgreSQL 数据库管理账号、邀请和兑换记录 - 支持在端内动态添加、移除管理员 - 完善 Docker 部署配置与 .gitignore 规则
This commit is contained in:
93
internal/chatgpt/invite.go
Normal file
93
internal/chatgpt/invite.go
Normal file
@@ -0,0 +1,93 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user