feat: Implement initial full-stack application structure including frontend pages, components, hooks, API integration, and backend services for account pooling and management.
This commit is contained in:
190
backend/internal/invite/team.go
Normal file
190
backend/internal/invite/team.go
Normal file
@@ -0,0 +1,190 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user