feat: 初始化 ChatGPT Team 管理机器人

核心功能:
- 实现基于 Telegram Inline Button 交互的后台面板与用户端
- 支持通过账密登录和 RT (Refresh Token) 方式添加 ChatGPT Team 账号
- 支持管理、拉取和删除待处理邀请,支持一键清空多余邀请
- 支持按剩余容量自动生成邀请兑换码,支持分页查看与一键清空未使用兑换码
- 随机邀请功能:成功拉人后自动核销兑换码
- 定时检测 Token 状态,实现自动续订/刷新并拦截封禁账号 (处理 401/402 错误)

系统与配置:
- 使用 PostgreSQL 数据库管理账号、邀请和兑换记录
- 支持在端内动态添加、移除管理员
- 完善 Docker 部署配置与 .gitignore 规则
This commit is contained in:
Sarteambot Admin
2026-03-04 20:08:34 +08:00
commit 0fde6d4a0b
19 changed files with 3893 additions and 0 deletions

View 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)
}