Files
GPTTeamBOT/internal/chatgpt/account.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

112 lines
2.9 KiB
Go

package chatgpt
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"go-helper/internal/model"
)
// FetchAccountInfo queries the ChatGPT accounts check API and returns team accounts
// with subscription expiry information.
func (c *Client) FetchAccountInfo(accessToken string) ([]model.TeamAccountInfo, error) {
token := strings.TrimPrefix(strings.TrimSpace(accessToken), "Bearer ")
if token == "" {
return nil, fmt.Errorf("缺少 access token")
}
apiURL := "https://chatgpt.com/backend-api/accounts/check/v4-2023-04-27"
req, err := http.NewRequest("GET", apiURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", "*/*")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
req.Header.Set("Oai-Client-Version", oaiClientVersion)
req.Header.Set("Oai-Language", "zh-CN")
req.Header.Set("User-Agent", userAgent)
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("请求 ChatGPT API 失败: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode == 401 || resp.StatusCode == 402 {
return nil, fmt.Errorf("Token 已过期或被封禁")
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("ChatGPT API 错误 %d: %s", resp.StatusCode, truncate(string(body), 300))
}
var data struct {
Accounts map[string]json.RawMessage `json:"accounts"`
AccountOrdering []string `json:"account_ordering"`
}
if err := json.Unmarshal(body, &data); err != nil {
return nil, fmt.Errorf("解析响应失败: %w", err)
}
var results []model.TeamAccountInfo
// Determine order.
seen := make(map[string]bool)
var orderedIDs []string
for _, id := range data.AccountOrdering {
if _, ok := data.Accounts[id]; ok && !seen[id] {
orderedIDs = append(orderedIDs, id)
seen[id] = true
}
}
for id := range data.Accounts {
if !seen[id] && id != "default" {
orderedIDs = append(orderedIDs, id)
}
}
for _, id := range orderedIDs {
raw := data.Accounts[id]
var acc struct {
Account struct {
Name string `json:"name"`
PlanType string `json:"plan_type"`
} `json:"account"`
Entitlement struct {
ExpiresAt string `json:"expires_at"`
HasActiveSubscription bool `json:"has_active_subscription"`
} `json:"entitlement"`
}
if err := json.Unmarshal(raw, &acc); err != nil {
continue
}
if acc.Account.PlanType != "team" {
continue
}
results = append(results, model.TeamAccountInfo{
AccountID: id,
Name: acc.Account.Name,
PlanType: acc.Account.PlanType,
ExpiresAt: acc.Entitlement.ExpiresAt,
HasActiveSubscription: acc.Entitlement.HasActiveSubscription,
})
}
if len(results) == 0 {
return nil, fmt.Errorf("未找到 Team 类型的账号")
}
return results, nil
}
func truncate(s string, max int) string {
if len(s) <= max {
return s
}
return s[:max] + "..."
}