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 }