196 lines
4.7 KiB
Go
196 lines
4.7 KiB
Go
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
|
||
}
|