416 lines
10 KiB
Go
416 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/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)
|
||
}
|
||
|
||
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 完整的注册流程
|
||
func Run(email, password, name, birthdate, proxy string) (*ChatGPTReg, error) {
|
||
return RunWithRetry(email, password, name, birthdate, proxy, 3)
|
||
}
|
||
|
||
// RunWithRetry 带重试的注册流程
|
||
// 当验证码获取超过5秒,就换新邮箱重新注册
|
||
func RunWithRetry(email, password, name, birthdate, proxy string, maxRetries int) (*ChatGPTReg, error) {
|
||
for attempt := 0; attempt < maxRetries; attempt++ {
|
||
if attempt > 0 {
|
||
// 重试时生成新邮箱
|
||
email = mail.GenerateEmail()
|
||
password = GeneratePassword()
|
||
fmt.Printf(" [Retry %d] New email: %s\n", attempt, email)
|
||
}
|
||
|
||
reg, err := runOnce(email, password, name, birthdate, proxy)
|
||
if err == nil {
|
||
return reg, nil
|
||
}
|
||
|
||
// 如果不是验证码超时错误,直接返回
|
||
if !strings.Contains(err.Error(), "验证码获取超时") {
|
||
return nil, err
|
||
}
|
||
|
||
fmt.Printf(" [!] OTP timeout, retrying with new email...\n")
|
||
}
|
||
|
||
return nil, fmt.Errorf("注册失败: 已重试 %d 次", maxRetries)
|
||
}
|
||
|
||
// runOnce 执行一次注册流程(使用短超时获取验证码)
|
||
func runOnce(email, password, name, birthdate, proxy string) (*ChatGPTReg, error) {
|
||
reg, err := New(proxy)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 初始化
|
||
if err := reg.InitSession(); err != nil {
|
||
return nil, fmt.Errorf("初始化失败: %v", err)
|
||
}
|
||
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)
|
||
}
|
||
|
||
// 先用5秒超时尝试获取验证码
|
||
otpCode, err := mail.GetVerificationCode(email, 5*time.Second)
|
||
if err != nil {
|
||
// 5秒内没获取到,再等120秒(总共等待更多时间)
|
||
otpCode, err = mail.GetVerificationCode(email, 120*time.Second)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("验证码获取超时")
|
||
}
|
||
}
|
||
|
||
if err := reg.ValidateOTP(otpCode); err != nil {
|
||
return nil, fmt.Errorf("OTP验证失败: %v", err)
|
||
}
|
||
|
||
// 创建账户
|
||
if err := reg.CreateAccount(name, birthdate); err != nil {
|
||
return nil, fmt.Errorf("创建账户失败: %v", err)
|
||
}
|
||
|
||
// 获取 Token
|
||
_ = reg.GetSessionToken()
|
||
|
||
return reg, nil
|
||
}
|
||
|
||
// ==================== 工具函数 ====================
|
||
|
||
// GenerateName 生成随机姓名
|
||
func GenerateName() string {
|
||
firstNames := []string{"James", "John", "Robert", "Michael", "David", "William", "Richard", "Joseph", "Thomas", "Charles"}
|
||
lastNames := []string{"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller", "Davis", "Rodriguez", "Martinez"}
|
||
return firstNames[rand.Intn(len(firstNames))] + " " + lastNames[rand.Intn(len(lastNames))]
|
||
}
|
||
|
||
// GenerateUUID 生成 UUID
|
||
func GenerateUUID() string {
|
||
b := make([]byte, 16)
|
||
rand.Read(b)
|
||
b[6] = (b[6] & 0x0f) | 0x40
|
||
b[8] = (b[8] & 0x3f) | 0x80
|
||
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
|
||
}
|
||
|
||
// GenerateBirthdate 生成随机生日
|
||
func GenerateBirthdate() string {
|
||
year := 2000 + rand.Intn(5)
|
||
month := 1 + rand.Intn(12)
|
||
day := 1 + rand.Intn(28)
|
||
return fmt.Sprintf("%d-%02d-%02d", year, month, day)
|
||
}
|
||
|
||
// GeneratePassword 生成随机密码
|
||
func GeneratePassword() string {
|
||
const (
|
||
upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||
lower = "abcdefghijklmnopqrstuvwxyz"
|
||
digits = "0123456789"
|
||
special = "!@#$%"
|
||
)
|
||
|
||
b := make([]byte, 13)
|
||
for i := 0; i < 2; i++ {
|
||
b[i] = upper[rand.Intn(len(upper))]
|
||
}
|
||
for i := 2; i < 10; i++ {
|
||
b[i] = lower[rand.Intn(len(lower))]
|
||
}
|
||
for i := 10; i < 12; i++ {
|
||
b[i] = digits[rand.Intn(len(digits))]
|
||
}
|
||
b[12] = special[rand.Intn(len(special))]
|
||
|
||
return string(b)
|
||
}
|
||
|
||
func truncateStr(s string, maxLen int) string {
|
||
if len(s) <= maxLen {
|
||
return s
|
||
}
|
||
return s[:maxLen] + "..."
|
||
}
|