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