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:
2026-01-30 07:40:35 +08:00
commit f4448bbef2
106 changed files with 19282 additions and 0 deletions

View 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
}