409 lines
10 KiB
Go
409 lines
10 KiB
Go
package register
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"fmt"
|
||
"math/rand"
|
||
"net/http"
|
||
"net/url"
|
||
"strings"
|
||
"time"
|
||
|
||
"codex-pool/internal/client"
|
||
"codex-pool/internal/logger"
|
||
"codex-pool/internal/mail"
|
||
)
|
||
|
||
// ChatGPTReg ChatGPT 注册器
|
||
type ChatGPTReg struct {
|
||
Proxy string
|
||
Client *client.TLSClient
|
||
AuthSessionLoggingID string
|
||
OAIDid string
|
||
CSRFToken string
|
||
AuthorizeURL string
|
||
AccessToken string
|
||
}
|
||
|
||
// Result 注册结果
|
||
type Result struct {
|
||
Email string `json:"email"`
|
||
Password string `json:"password"`
|
||
Name string `json:"name"`
|
||
AccessToken string `json:"access_token"`
|
||
}
|
||
|
||
// New 创建注册器
|
||
func New(proxy string) (*ChatGPTReg, error) {
|
||
c, err := client.New(proxy)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return &ChatGPTReg{
|
||
Proxy: proxy,
|
||
Client: c,
|
||
AuthSessionLoggingID: GenerateUUID(),
|
||
}, nil
|
||
}
|
||
|
||
// InitSession 初始化会话
|
||
func (r *ChatGPTReg) InitSession() error {
|
||
resp, err := r.Client.Get("https://chatgpt.com")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != 200 {
|
||
return fmt.Errorf("初始化失败,状态码: %d", resp.StatusCode)
|
||
}
|
||
|
||
r.OAIDid = r.Client.GetCookie("https://chatgpt.com", "oai-did")
|
||
|
||
csrfCookie := r.Client.GetCookie("https://chatgpt.com", "__Host-next-auth.csrf-token")
|
||
if csrfCookie != "" {
|
||
decoded, err := url.QueryUnescape(csrfCookie)
|
||
if err == nil {
|
||
parts := strings.Split(decoded, "|")
|
||
if len(parts) > 0 {
|
||
r.CSRFToken = parts[0]
|
||
}
|
||
}
|
||
}
|
||
|
||
if r.CSRFToken == "" {
|
||
return fmt.Errorf("无法获取 CSRF token")
|
||
}
|
||
|
||
loginURL := fmt.Sprintf("https://chatgpt.com/auth/login?openaicom-did=%s", r.OAIDid)
|
||
loginResp, err := r.Client.Get(loginURL)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer loginResp.Body.Close()
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetAuthorizeURL 获取授权 URL
|
||
func (r *ChatGPTReg) GetAuthorizeURL(email string) error {
|
||
loginURL := fmt.Sprintf(
|
||
"https://chatgpt.com/api/auth/signin/openai?prompt=login&ext-oai-did=%s&auth_session_logging_id=%s&screen_hint=login_or_signup&login_hint=%s",
|
||
r.OAIDid,
|
||
r.AuthSessionLoggingID,
|
||
url.QueryEscape(email),
|
||
)
|
||
|
||
data := url.Values{}
|
||
data.Set("callbackUrl", "https://chatgpt.com/")
|
||
data.Set("csrfToken", r.CSRFToken)
|
||
data.Set("json", "true")
|
||
|
||
req, err := http.NewRequest("POST", loginURL, strings.NewReader(data.Encode()))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
req.Header.Set("Origin", "https://chatgpt.com")
|
||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||
|
||
resp, err := r.Client.Do(req)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
var result map[string]interface{}
|
||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||
return err
|
||
}
|
||
|
||
if authURL, ok := result["url"].(string); ok && strings.Contains(authURL, "auth.openai.com") {
|
||
r.AuthorizeURL = authURL
|
||
return nil
|
||
}
|
||
|
||
return fmt.Errorf("无法获取授权 URL")
|
||
}
|
||
|
||
// StartAuthorize 开始授权流程
|
||
func (r *ChatGPTReg) StartAuthorize() error {
|
||
resp, err := r.Client.Get(r.AuthorizeURL)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
finalURL := resp.Request.URL.String()
|
||
if strings.Contains(finalURL, "create-account") || strings.Contains(finalURL, "log-in") {
|
||
return nil
|
||
}
|
||
|
||
return fmt.Errorf("授权流程启动失败")
|
||
}
|
||
|
||
// Register 注册账户
|
||
func (r *ChatGPTReg) Register(email, password string) error {
|
||
payload := map[string]string{
|
||
"password": password,
|
||
"username": email,
|
||
}
|
||
jsonData, _ := json.Marshal(payload)
|
||
|
||
req, err := http.NewRequest("POST", "https://auth.openai.com/api/accounts/user/register", bytes.NewReader(jsonData))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
req.Header.Set("Content-Type", "application/json")
|
||
req.Header.Set("Origin", "https://auth.openai.com")
|
||
|
||
resp, err := r.Client.Do(req)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != 200 {
|
||
body, _ := client.ReadBodyString(resp)
|
||
return fmt.Errorf("注册失败,状态码: %d, 响应: %s", resp.StatusCode, truncateStr(body, 200))
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// SendVerificationEmail 发送验证邮件
|
||
func (r *ChatGPTReg) SendVerificationEmail() error {
|
||
resp, err := r.Client.Get("https://auth.openai.com/api/accounts/email-otp/send")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != 200 {
|
||
return fmt.Errorf("发送验证邮件失败,状态码: %d", resp.StatusCode)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// ValidateOTP 验证 OTP
|
||
func (r *ChatGPTReg) ValidateOTP(code string) error {
|
||
payload := map[string]string{"code": code}
|
||
jsonData, _ := json.Marshal(payload)
|
||
|
||
req, err := http.NewRequest("POST", "https://auth.openai.com/api/accounts/email-otp/validate", bytes.NewReader(jsonData))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
req.Header.Set("Content-Type", "application/json")
|
||
req.Header.Set("Origin", "https://auth.openai.com")
|
||
|
||
resp, err := r.Client.Do(req)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != 200 {
|
||
return fmt.Errorf("OTP 验证失败,状态码: %d", resp.StatusCode)
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// CreateAccount 创建账户
|
||
func (r *ChatGPTReg) CreateAccount(name, birthdate string) error {
|
||
payload := map[string]string{
|
||
"name": name,
|
||
"birthdate": birthdate,
|
||
}
|
||
jsonData, _ := json.Marshal(payload)
|
||
|
||
req, err := http.NewRequest("POST", "https://auth.openai.com/api/accounts/create_account", bytes.NewReader(jsonData))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
req.Header.Set("Content-Type", "application/json")
|
||
req.Header.Set("Origin", "https://auth.openai.com")
|
||
|
||
resp, err := r.Client.Do(req)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != 200 {
|
||
return fmt.Errorf("创建账户失败,状态码: %d", resp.StatusCode)
|
||
}
|
||
|
||
// 处理可能出现的继续 URL
|
||
var result map[string]interface{}
|
||
if err := json.NewDecoder(resp.Body).Decode(&result); err == nil {
|
||
if continueURL, ok := result["continue_url"].(string); ok && continueURL != "" {
|
||
contResp, err := r.Client.Get(continueURL)
|
||
if err == nil {
|
||
contResp.Body.Close()
|
||
}
|
||
}
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
// GetSessionToken 获取 access token
|
||
func (r *ChatGPTReg) GetSessionToken() error {
|
||
resp, err := r.Client.Get("https://chatgpt.com/api/auth/session")
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != 200 {
|
||
return fmt.Errorf("获取 session 失败,状态码: %d", resp.StatusCode)
|
||
}
|
||
|
||
var result map[string]interface{}
|
||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||
return err
|
||
}
|
||
|
||
if token, ok := result["accessToken"].(string); ok {
|
||
r.AccessToken = token
|
||
return nil
|
||
}
|
||
|
||
return fmt.Errorf("响应中没有 accessToken")
|
||
}
|
||
|
||
// Run 执行单次注册(由 main.go 调用)
|
||
func Run(email, password, realName, birthdate, proxy string) (*ChatGPTReg, error) {
|
||
return APIRegister(email, password, realName, birthdate, proxy, "[RegTest]")
|
||
}
|
||
|
||
// APIRegister 使用 API 完成注册 (集成 403 重试机制)
|
||
func APIRegister(email, password, realName, birthdate, proxy string, logPrefix string) (*ChatGPTReg, error) {
|
||
var reg *ChatGPTReg
|
||
var lastErr error
|
||
|
||
// 403 重试机制 - 最多重试 3 次,每次换新指纹
|
||
for retry := 0; retry < 3; retry++ {
|
||
var err error
|
||
reg, err = New(proxy)
|
||
if err != nil {
|
||
lastErr = err
|
||
if retry < 2 {
|
||
logger.Warning(fmt.Sprintf("%s 客户端创建失败,重试 %d/3...", logPrefix, retry+1), email, "register")
|
||
continue
|
||
}
|
||
return nil, fmt.Errorf("客户端创建失败: %v", err)
|
||
}
|
||
|
||
fpInfo := reg.Client.GetFingerprintInfo()
|
||
if retry == 0 {
|
||
logger.Status(fmt.Sprintf("%s 初始化会话... [%s]", logPrefix, fpInfo), email, "register")
|
||
} else {
|
||
logger.Warning(fmt.Sprintf("%s 403 重试 %d/3,换指纹 [%s]", logPrefix, retry, fpInfo), email, "register")
|
||
}
|
||
|
||
if err := reg.InitSession(); err != nil {
|
||
if strings.Contains(err.Error(), "403") {
|
||
lastErr = err
|
||
reg.Client.Close()
|
||
continue // 403 则换指纹重试
|
||
}
|
||
return nil, fmt.Errorf("初始化失败: %v", err)
|
||
}
|
||
|
||
// 初始化成功
|
||
lastErr = nil
|
||
break
|
||
}
|
||
|
||
if lastErr != nil {
|
||
return nil, fmt.Errorf("初始化失败: %v (已重试3次)", lastErr)
|
||
}
|
||
|
||
// 获取授权 URL
|
||
if err := reg.GetAuthorizeURL(email); err != nil {
|
||
return nil, fmt.Errorf("获取授权URL失败: %v", err)
|
||
}
|
||
|
||
// 开始授权流程
|
||
if err := reg.StartAuthorize(); err != nil {
|
||
return nil, fmt.Errorf("授权流程失败: %v", err)
|
||
}
|
||
|
||
// 注册账户
|
||
if err := reg.Register(email, password); err != nil {
|
||
return nil, fmt.Errorf("注册账号失败: %v", err)
|
||
}
|
||
|
||
// 发送验证邮件
|
||
if err := reg.SendVerificationEmail(); err != nil {
|
||
return nil, fmt.Errorf("发送验证邮件失败: %v", err)
|
||
}
|
||
|
||
// 获取验证码 (带超时 60s) - 合并日志,使用 Status 显示等待状态
|
||
logger.Status(fmt.Sprintf("%s 验证邮箱中...", logPrefix), email, "register")
|
||
otpCode, err := mail.GetVerificationCode(email, 60*time.Second)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
logger.Success(fmt.Sprintf("%s 验证码: %s", logPrefix, otpCode), email, "register")
|
||
|
||
// 验证 OTP
|
||
if err := reg.ValidateOTP(otpCode); err != nil {
|
||
return nil, fmt.Errorf("验证OTP失败: %v", err)
|
||
}
|
||
|
||
// 创建账户
|
||
if err := reg.CreateAccount(realName, birthdate); err != nil {
|
||
return nil, fmt.Errorf("创建账户失败: %v", err)
|
||
}
|
||
|
||
// 预热 session (可选)
|
||
_ = reg.GetSessionToken()
|
||
|
||
return reg, nil
|
||
}
|
||
|
||
// GenerateUUID 生成 UUID
|
||
func GenerateUUID() string {
|
||
b := make([]byte, 16)
|
||
rand.Read(b)
|
||
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
|
||
}
|
||
|
||
// GenerateName 随机生成姓名
|
||
func GenerateName() string {
|
||
firstNames := []string{"James", "Mary", "John", "Patricia", "Robert", "Jennifer", "Michael", "Linda", "William", "Elizabeth"}
|
||
lastNames := []string{"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez"}
|
||
return firstNames[rand.Intn(len(firstNames))] + " " + lastNames[rand.Intn(len(lastNames))]
|
||
}
|
||
|
||
// GenerateBirthdate 随机生成生日
|
||
func GenerateBirthdate() string {
|
||
year := rand.Intn(20) + 1980
|
||
month := rand.Intn(12) + 1
|
||
day := rand.Intn(28) + 1
|
||
return fmt.Sprintf("%04d-%02d-%02d", year, month, day)
|
||
}
|
||
|
||
// GeneratePassword 随机生成密码
|
||
func GeneratePassword() string {
|
||
chars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*"
|
||
p := make([]byte, 12)
|
||
for i := range p {
|
||
p[i] = chars[rand.Intn(len(chars))]
|
||
}
|
||
return string(p)
|
||
}
|
||
|
||
func truncateStr(s string, max int) string {
|
||
if len(s) <= max {
|
||
return s
|
||
}
|
||
return s[:max] + "..."
|
||
}
|