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