Files
gpt-plus-gpt/pkg/chatgpt/invite.go
2026-03-15 20:48:19 +08:00

120 lines
3.5 KiB
Go

package chatgpt
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"gpt-plus/pkg/httpclient"
)
// InviteResult holds the result of a team member invitation.
type InviteResult struct {
Email string
Success bool
Error string
}
// GetWorkspaceAccessToken gets a workspace-scoped access token by calling
// GET /api/auth/session with chatgpt-account-id header.
// This is simpler than the exchange_workspace_token approach and matches browser behavior.
func GetWorkspaceAccessToken(client *httpclient.Client, teamAccountID string) (string, error) {
sessionURL := "https://chatgpt.com/api/auth/session"
headers := map[string]string{
"User-Agent": defaultUserAgent,
"Accept": "application/json",
"Origin": chatGPTOrigin,
"Referer": chatGPTOrigin + "/",
"chatgpt-account-id": teamAccountID,
}
resp, err := client.Get(sessionURL, headers)
if err != nil {
return "", fmt.Errorf("workspace session request: %w", err)
}
body, err := httpclient.ReadBody(resp)
if err != nil {
return "", fmt.Errorf("read workspace session body: %w", err)
}
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("workspace session returned %d: %s", resp.StatusCode, string(body))
}
var result struct {
AccessToken string `json:"accessToken"`
}
if err := json.Unmarshal(body, &result); err != nil {
return "", fmt.Errorf("parse workspace session response: %w", err)
}
if result.AccessToken == "" {
return "", fmt.Errorf("empty accessToken in workspace session response")
}
log.Printf("[invite] workspace access token obtained for %s, len=%d", teamAccountID, len(result.AccessToken))
return result.AccessToken, nil
}
// InviteToTeam sends a team invitation to the specified email address.
// POST /backend-api/accounts/{account_id}/invites
func InviteToTeam(client *httpclient.Client, workspaceToken, teamAccountID, deviceID, email string) error {
inviteURL := fmt.Sprintf("https://chatgpt.com/backend-api/accounts/%s/invites", teamAccountID)
payload := map[string]interface{}{
"email_addresses": []string{email},
"role": "standard-user",
"resend_emails": true,
}
jsonBody, err := json.Marshal(payload)
if err != nil {
return fmt.Errorf("marshal invite body: %w", err)
}
req, err := http.NewRequest("POST", inviteURL, strings.NewReader(string(jsonBody)))
if err != nil {
return fmt.Errorf("create invite request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+workspaceToken)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", defaultUserAgent)
req.Header.Set("Origin", chatGPTOrigin)
req.Header.Set("Referer", chatGPTOrigin+"/")
req.Header.Set("oai-device-id", deviceID)
req.Header.Set("oai-language", defaultLanguage)
req.Header.Set("chatgpt-account-id", teamAccountID)
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("send invite request: %w", err)
}
defer resp.Body.Close()
body, err := httpclient.ReadBody(resp)
if err != nil {
return fmt.Errorf("read invite response: %w", err)
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("invite returned %d: %s", resp.StatusCode, string(body))
}
// Verify response contains account_invites
var result map[string]interface{}
if err := json.Unmarshal(body, &result); err != nil {
return fmt.Errorf("parse invite response: %w", err)
}
if _, ok := result["account_invites"]; !ok {
return fmt.Errorf("no account_invites in response: %s", string(body))
}
log.Printf("[invite] successfully invited %s to team %s", email, teamAccountID)
return nil
}