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

系统与配置:
- 使用 PostgreSQL 数据库管理账号、邀请和兑换记录
- 支持在端内动态添加、移除管理员
- 完善 Docker 部署配置与 .gitignore 规则
2026-03-04 20:08:34 +08:00

196 lines
5.5 KiB
Go

package chatgpt
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strings"
"go-helper/internal/model"
)
// GetUsers fetches the list of members for a team account.
func (c *Client) GetUsers(account *model.GptAccount) (int, []model.ChatGPTUser, error) {
apiURL := fmt.Sprintf("https://chatgpt.com/backend-api/accounts/%s/users?offset=0&limit=100&query=",
account.ChatgptAccountID)
req, err := http.NewRequest("GET", apiURL, nil)
if err != nil {
return 0, nil, err
}
req.Header = buildHeaders(account.Token, account.ChatgptAccountID)
resp, err := c.httpClient.Do(req)
if err != nil {
return 0, nil, fmt.Errorf("获取成员列表网络错误: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if err := checkAPIError(resp.StatusCode, body, "获取成员"); err != nil {
return 0, nil, err
}
var data struct {
Total int `json:"total"`
Items []struct {
ID string `json:"id"`
AccountUserID string `json:"account_user_id"`
Email string `json:"email"`
Role string `json:"role"`
Name string `json:"name"`
} `json:"items"`
}
if err := json.Unmarshal(body, &data); err != nil {
return 0, nil, fmt.Errorf("解析成员数据失败: %w", err)
}
var users []model.ChatGPTUser
for _, item := range data.Items {
users = append(users, model.ChatGPTUser{
ID: item.ID,
AccountUserID: item.AccountUserID,
Email: item.Email,
Role: item.Role,
Name: item.Name,
})
}
return data.Total, users, nil
}
// DeleteUser removes a user from the team account.
func (c *Client) DeleteUser(account *model.GptAccount, userID string) error {
normalizedID := userID
if !strings.HasPrefix(normalizedID, "user-") {
normalizedID = "user-" + normalizedID
}
apiURL := fmt.Sprintf("https://chatgpt.com/backend-api/accounts/%s/users/%s",
account.ChatgptAccountID, normalizedID)
req, err := http.NewRequest("DELETE", apiURL, nil)
if err != nil {
return err
}
req.Header = buildHeaders(account.Token, account.ChatgptAccountID)
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("删除用户网络错误: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if err := checkAPIError(resp.StatusCode, body, "删除用户"); err != nil {
return err
}
log.Printf("[Member] 成功删除用户 %s (账号 %s)", normalizedID, account.ChatgptAccountID)
return nil
}
// GetInvites fetches pending invitations for a team account.
func (c *Client) GetInvites(account *model.GptAccount) (int, []model.ChatGPTInvite, error) {
apiURL := fmt.Sprintf("https://chatgpt.com/backend-api/accounts/%s/invites?offset=0&limit=100&query=",
account.ChatgptAccountID)
req, err := http.NewRequest("GET", apiURL, nil)
if err != nil {
return 0, nil, err
}
req.Header = buildHeaders(account.Token, account.ChatgptAccountID)
req.Header.Set("Referer", "https://chatgpt.com/admin/members?tab=invites")
resp, err := c.httpClient.Do(req)
if err != nil {
return 0, nil, fmt.Errorf("获取邀请列表网络错误: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if err := checkAPIError(resp.StatusCode, body, "获取邀请列表"); err != nil {
return 0, nil, err
}
var data struct {
Total int `json:"total"`
Items []struct {
ID string `json:"id"`
EmailAddress string `json:"email_address"`
Role string `json:"role"`
} `json:"items"`
}
if err := json.Unmarshal(body, &data); err != nil {
return 0, nil, fmt.Errorf("解析邀请数据失败: %w", err)
}
var invites []model.ChatGPTInvite
for _, item := range data.Items {
invites = append(invites, model.ChatGPTInvite{
ID: item.ID,
EmailAddress: item.EmailAddress,
Role: item.Role,
})
}
return data.Total, invites, nil
}
// DeleteInvite cancels a pending invitation by email on the team account.
func (c *Client) DeleteInvite(account *model.GptAccount, email string) error {
apiURL := fmt.Sprintf("https://chatgpt.com/backend-api/accounts/%s/invites",
account.ChatgptAccountID)
payload := map[string]string{"email_address": strings.TrimSpace(strings.ToLower(email))}
bodyBytes, _ := json.Marshal(payload)
req, err := http.NewRequest("DELETE", apiURL, bytes.NewReader(bodyBytes))
if err != nil {
return err
}
req.Header = buildHeaders(account.Token, account.ChatgptAccountID)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Referer", "https://chatgpt.com/admin/members?tab=invites")
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("删除邀请网络错误: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if err := checkAPIError(resp.StatusCode, body, "删除邀请"); err != nil {
return err
}
log.Printf("[Member] 成功删除邀请 %s (账号 %s)", email, account.ChatgptAccountID)
return nil
}
// checkAPIError inspects the HTTP status code and returns a descriptive error.
func checkAPIError(statusCode int, body []byte, label string) error {
if statusCode >= 200 && statusCode < 300 {
return nil
}
bodyStr := truncate(string(body), 500)
// Check for account_deactivated.
if strings.Contains(bodyStr, "account_deactivated") {
return fmt.Errorf("账号已停用 (account_deactivated)")
}
switch statusCode {
case 401, 402:
return fmt.Errorf("Token 已过期或被封禁")
case 403:
return fmt.Errorf("资源不存在或无权访问")
case 429:
return fmt.Errorf("API 请求过于频繁,请稍后重试")
default:
return fmt.Errorf("%s失败 (HTTP %d): %s", label, statusCode, bodyStr)
}
}