Files
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

110 lines
2.6 KiB
Go

package chatgpt
import (
"fmt"
"io"
"net/http"
"net/url"
"strings"
"encoding/json"
)
// TokenResult holds the result of a token refresh.
type TokenResult struct {
AccessToken string
RefreshToken string
IDToken string
ExpiresIn int
}
// GetEmail extracts the user's email from the ID token or Access token.
func (tr *TokenResult) GetEmail() string {
if tr.IDToken != "" {
claims, err := decodeJWTPayload(tr.IDToken)
if err == nil {
if email, ok := claims["email"].(string); ok && email != "" {
return email
}
}
}
if tr.AccessToken != "" {
claims, err := decodeJWTPayload(tr.AccessToken)
if err == nil {
if email, ok := claims["email"].(string); ok && email != "" {
return email
}
}
}
return ""
}
// RefreshAccessToken exchanges a refresh token for a new access token.
func (c *Client) RefreshAccessToken(refreshToken string) (*TokenResult, error) {
rt := strings.TrimSpace(refreshToken)
if rt == "" {
return nil, fmt.Errorf("refresh token 为空")
}
form := url.Values{}
form.Set("grant_type", "refresh_token")
form.Set("client_id", openaiClientID)
form.Set("refresh_token", rt)
form.Set("scope", "openid profile email")
req, err := http.NewRequest("POST", "https://auth.openai.com/oauth/token", strings.NewReader(form.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("刷新 token 网络错误: %w", err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if resp.StatusCode != 200 {
var errData struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
_ = json.Unmarshal(body, &errData)
msg := errData.ErrorDescription
if msg == "" {
msg = errData.Error
}
if msg == "" {
msg = fmt.Sprintf("HTTP %d", resp.StatusCode)
}
return nil, fmt.Errorf("刷新 token 失败: %s", msg)
}
var result struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
IDToken string `json:"id_token"`
ExpiresIn int `json:"expires_in"`
}
if err := json.Unmarshal(body, &result); err != nil {
return nil, fmt.Errorf("解析刷新结果失败: %w", err)
}
if result.AccessToken == "" {
return nil, fmt.Errorf("刷新 token 失败: 未返回有效凭证")
}
newRT := result.RefreshToken
if newRT == "" {
newRT = rt
}
return &TokenResult{
AccessToken: result.AccessToken,
RefreshToken: newRT,
IDToken: result.IDToken,
ExpiresIn: result.ExpiresIn,
}, nil
}