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 }