120 lines
3.5 KiB
Go
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
|
|
}
|