package invite import ( "bytes" "encoding/json" "fmt" "io" "net/http" "codex-pool/internal/client" ) // DefaultProxy 默认代理 const DefaultProxy = "http://127.0.0.1:7890" // TeamInviter Team 邀请器 type TeamInviter struct { client *client.TLSClient accessToken string accountID string } // InviteRequest 邀请请求 type InviteRequest struct { EmailAddresses []string `json:"email_addresses"` Role string `json:"role"` ResendEmails bool `json:"resend_emails"` } // AccountCheckResponse 账号检查响应 type AccountCheckResponse struct { Accounts map[string]struct { Account struct { PlanType string `json:"plan_type"` } `json:"account"` } `json:"accounts"` } // New 创建邀请器 (使用默认代理) func New(accessToken string) *TeamInviter { c, _ := client.New(DefaultProxy) return &TeamInviter{ client: c, accessToken: accessToken, } } // NewWithProxy 创建邀请器 (指定代理) func NewWithProxy(accessToken, proxy string) *TeamInviter { c, _ := client.New(proxy) return &TeamInviter{ client: c, accessToken: accessToken, } } // SetAccountID 手动设置 account_id(当已有存储值时使用) func (t *TeamInviter) SetAccountID(accountID string) { t.accountID = accountID } // GetAccountID 获取 Team 的 account_id (workspace_id) func (t *TeamInviter) GetAccountID() (string, error) { req, _ := http.NewRequest("GET", "https://chatgpt.com/backend-api/accounts/check/v4-2023-04-27", nil) req.Header.Set("Authorization", "Bearer "+t.accessToken) req.Header.Set("Accept", "application/json") resp, err := t.client.Do(req) if err != nil { return "", fmt.Errorf("请求失败: %v", err) } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) if resp.StatusCode != 200 { return "", fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)[:min(200, len(body))]) } var result AccountCheckResponse if err := json.Unmarshal(body, &result); err != nil { return "", fmt.Errorf("解析失败: %v", err) } // 查找 team plan 的 account_id for accountID, info := range result.Accounts { if accountID != "default" && info.Account.PlanType == "team" { t.accountID = accountID return accountID, nil } } // 如果没找到 team,返回第一个非 default 的 for accountID := range result.Accounts { if accountID != "default" { t.accountID = accountID return accountID, nil } } return "", fmt.Errorf("未找到 account_id") } // SendInvites 发送邀请 func (t *TeamInviter) SendInvites(emails []string) error { if t.accountID == "" { return fmt.Errorf("未设置 account_id,请先调用 GetAccountID()") } url := fmt.Sprintf("https://chatgpt.com/backend-api/accounts/%s/invites", t.accountID) payload := InviteRequest{ EmailAddresses: emails, Role: "standard-user", ResendEmails: true, } body, _ := json.Marshal(payload) req, _ := http.NewRequest("POST", url, bytes.NewReader(body)) req.Header.Set("Authorization", "Bearer "+t.accessToken) req.Header.Set("Content-Type", "application/json") req.Header.Set("Chatgpt-Account-Id", t.accountID) resp, err := t.client.Do(req) if err != nil { return fmt.Errorf("请求失败: %v", err) } defer resp.Body.Close() respBody, _ := io.ReadAll(resp.Body) if resp.StatusCode != 200 && resp.StatusCode != 201 { return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(respBody)[:min(200, len(respBody))]) } return nil } // GetPendingInvites 获取待处理的邀请列表 func (t *TeamInviter) GetPendingInvites() ([]string, error) { if t.accountID == "" { return nil, fmt.Errorf("未设置 account_id") } url := fmt.Sprintf("https://chatgpt.com/backend-api/accounts/%s/invites?offset=0&limit=100&query=", t.accountID) req, _ := http.NewRequest("GET", url, nil) req.Header.Set("Authorization", "Bearer "+t.accessToken) resp, err := t.client.Do(req) if err != nil { return nil, err } defer resp.Body.Close() body, _ := io.ReadAll(resp.Body) var result struct { Invites []struct { Email string `json:"email"` } `json:"invites"` } if err := json.Unmarshal(body, &result); err != nil { return nil, err } var emails []string for _, inv := range result.Invites { emails = append(emails, inv.Email) } return emails, nil } // AcceptInvite 接受邀请 (使用被邀请账号的 token) func AcceptInvite(inviteLink string, accessToken string) error { c, _ := client.New(DefaultProxy) req, _ := http.NewRequest("GET", inviteLink, nil) req.Header.Set("Authorization", "Bearer "+accessToken) resp, err := c.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != 200 && resp.StatusCode != 302 { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)[:min(100, len(body))]) } return nil }