Initial commit: ChatGPT Account Registration Bot

This commit is contained in:
verssache
2026-03-10 23:18:19 +07:00
commit cd2890fb21
17 changed files with 1493 additions and 0 deletions

View File

@@ -0,0 +1,48 @@
package chrome
import (
"fmt"
"math/rand"
"time"
"github.com/bogdanfinn/tls-client/profiles"
)
type Profile struct {
Major int
Impersonate string
Build int
PatchMin int
PatchMax int
SecChUA string
}
var chromeProfiles = []Profile{
{131, "chrome131", 6778, 0, 300, "\"Chromium\";v=\"131\", \"Google Chrome\";v=\"131\", \"Not_A Brand\";v=\"24\""},
}
func RandomChromeVersion() (Profile, string, string) {
rand.Seed(time.Now().UnixNano())
profile := chromeProfiles[rand.Intn(len(chromeProfiles))]
patch := rand.Intn(profile.PatchMax-profile.PatchMin+1) + profile.PatchMin
fullVersion := fmt.Sprintf("%d.0.%d.%d", profile.Major, profile.Build, patch)
userAgent := fmt.Sprintf("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/%s Safari/537.36", fullVersion)
return profile, fullVersion, userAgent
}
func MapToTLSProfile(impersonate string) profiles.ClientProfile {
switch impersonate {
case "chrome131":
return profiles.Chrome_131
case "chrome133a":
return profiles.Chrome_133
case "chrome136":
// Fallback to Chrome_133 as Chrome_136 is not available in tls-client v1.14.0
return profiles.Chrome_133
case "chrome142":
// Fallback to Chrome_133 as Chrome_142 is not available in tls-client v1.14.0
return profiles.Chrome_133
default:
return profiles.Chrome_133
}
}

59
internal/config/config.go Normal file
View File

@@ -0,0 +1,59 @@
package config
import (
"encoding/json"
"os"
"fmt"
)
// Config holds the application configuration.
type Config struct {
Proxy string `json:"proxy"`
OutputFile string `json:"output_file"`
DefaultPassword string `json:"default_password"`
DefaultDomain string `json:"default_domain"`
}
const (
DefaultProxy = ""
DefaultOutputFile = "results.txt"
DefaultConfigFilename = "config.json"
DefaultPassword = "" // Min 12 characters
DefaultDomainValue = ""
)
// DefaultConfigPath returns the default path to the config file.
func DefaultConfigPath() string {
return DefaultConfigFilename
}
// Load reads the config from a JSON file and applies environment variable overrides.
func Load(path string) (*Config, error) {
cfg := &Config{
Proxy: DefaultProxy,
OutputFile: DefaultOutputFile,
DefaultPassword: DefaultPassword,
DefaultDomain: DefaultDomainValue,
}
// Try to read the file
data, err := os.ReadFile(path)
if err == nil {
if err := json.Unmarshal(data, cfg); err != nil {
return nil, err
}
} else if !os.IsNotExist(err) {
return nil, err
}
// Validate password length
if cfg.DefaultPassword != "" && len(cfg.DefaultPassword) < 12 {
return nil, fmt.Errorf("default_password must be at least 12 characters (got %d)", len(cfg.DefaultPassword))
}
// Environment variable overrides
if proxy := os.Getenv("PROXY"); proxy != "" {
cfg.Proxy = proxy
}
return cfg, nil
}

153
internal/email/generator.go Normal file
View File

@@ -0,0 +1,153 @@
package email
import (
"fmt"
"math/rand"
"net/http"
"regexp"
"strings"
"time"
"github.com/brianvoe/gofakeit/v7"
"github.com/PuerkitoBio/goquery"
fhttp "github.com/bogdanfinn/fhttp"
"github.com/bogdanfinn/tls-client"
"github.com/bogdanfinn/tls-client/profiles"
"github.com/verssache/chatgpt-creator/internal/util"
)
// CreateTempEmail fetches a new temp email using a random profile and gofakeit names.
func CreateTempEmail(defaultDomain string) (string, error) {
// If defaultDomain is set, skip fetching from generator.email
if defaultDomain != "" {
firstName := gofakeit.FirstName()
lastName := gofakeit.LastName()
email := strings.ToLower(firstName+lastName+util.RandStr(5)) + "@" + defaultDomain
return email, nil
}
options := []tls_client.HttpClientOption{
tls_client.WithClientProfile(profiles.Chrome_131),
}
client, err := tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...)
if err != nil {
return "", fmt.Errorf("failed to create tls client: %w", err)
}
req, err := fhttp.NewRequest(http.MethodGet, "https://generator.email/", nil)
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
resp, err := client.Do(req)
if err != nil {
return "", fmt.Errorf("failed to fetch generator.email: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("generator.email returned status: %d", resp.StatusCode)
}
doc, err := goquery.NewDocumentFromReader(resp.Body)
if err != nil {
return "", fmt.Errorf("failed to parse HTML: %w", err)
}
domains := []string{"smartmail.de", "enayu.com", "crazymailing.com"}
doc.Find(".e7m.tt-suggestions div > p").Each(func(i int, s *goquery.Selection) {
domain := strings.TrimSpace(s.Text())
if domain != "" {
domains = append(domains, domain)
}
})
r := rand.New(rand.NewSource(time.Now().UnixNano()))
randomDomain := domains[r.Intn(len(domains))]
firstName := gofakeit.FirstName()
lastName := gofakeit.LastName()
email := strings.ToLower(firstName+lastName+util.RandStr(5)) + "@" + randomDomain
return email, nil
}
// GetVerificationCode polls generator.email for the OTP using a custom cookie.
func GetVerificationCode(email string, maxRetries int, delay time.Duration) (string, error) {
parts := strings.Split(email, "@")
if len(parts) != 2 {
return "", fmt.Errorf("invalid email format: %s", email)
}
username := parts[0]
domain := parts[1]
otpRegex := regexp.MustCompile(`\d{6}`)
for i := 0; i < maxRetries; i++ {
options := []tls_client.HttpClientOption{
tls_client.WithClientProfile(profiles.Chrome_131),
}
client, err := tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...)
if err != nil {
return "", fmt.Errorf("failed to create tls client: %w", err)
}
url := fmt.Sprintf("https://generator.email/%s/%s", domain, username)
req, err := fhttp.NewRequest(http.MethodGet, url, nil)
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
// Critical: Set request header Cookie: surl={domain}/{username} explicitly.
req.Header.Set("Cookie", fmt.Sprintf("surl=%s/%s", domain, username))
resp, err := client.Do(req)
if err != nil {
// Log error and continue retrying
time.Sleep(delay)
continue
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
time.Sleep(delay)
continue
}
doc, err := goquery.NewDocumentFromReader(resp.Body)
resp.Body.Close()
if err != nil {
time.Sleep(delay)
continue
}
// Find OTP in #email-table > div.e7m.list-group-item.list-group-item-info > div.e7m.subj_div_45g45gg
otp := ""
doc.Find("#email-table > div.e7m.list-group-item.list-group-item-info > div.e7m.subj_div_45g45gg").EachWithBreak(func(i int, s *goquery.Selection) bool {
text := s.Text()
matches := otpRegex.FindStringSubmatch(text)
if len(matches) > 0 {
code := matches[0]
// Skip code "177010" explicitly
if code == "177010" {
return true // continue to next if any, but we only expect one
}
otp = code
return false // break
}
return true
})
if otp != "" {
return otp, nil
}
time.Sleep(delay)
}
return "", fmt.Errorf("failed to get verification code after %d retries", maxRetries)
}

134
internal/register/batch.go Normal file
View File

@@ -0,0 +1,134 @@
package register
import (
"fmt"
"os"
"sync"
"sync/atomic"
"time"
"github.com/verssache/chatgpt-creator/internal/email"
"github.com/verssache/chatgpt-creator/internal/util"
)
// registerOne handles a single account registration.
func registerOne(workerID int, tag string, proxy, outputFile, defaultPassword, defaultDomain string, printMu, fileMu *sync.Mutex) (bool, string, string) {
client, err := NewClient(proxy, tag, workerID, printMu, fileMu)
if err != nil {
return false, "", fmt.Sprintf("failed to create client: %v", err)
}
emailAddr, err := email.CreateTempEmail(defaultDomain)
if err != nil {
return false, "", fmt.Sprintf("failed to create temp email: %v", err)
}
password := defaultPassword
if password == "" {
password = util.GeneratePassword(14)
}
firstName, lastName := util.RandomName()
birthdate := util.RandomBirthdate()
client.print(fmt.Sprintf("Starting registration for %s", emailAddr))
err = client.RunRegister(emailAddr, password, firstName+" "+lastName, birthdate)
if err != nil {
return false, emailAddr, err.Error()
}
// Append to file
fileMu.Lock()
defer fileMu.Unlock()
f, err := os.OpenFile(outputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return false, emailAddr, fmt.Sprintf("failed to open output file: %v", err)
}
defer f.Close()
line := fmt.Sprintf("%s|%s\n", emailAddr, password)
if _, err := f.WriteString(line); err != nil {
return false, emailAddr, fmt.Sprintf("failed to write to output file: %v", err)
}
return true, emailAddr, ""
}
// RunBatch runs concurrent registration tasks with retry until target success count is reached.
func RunBatch(totalAccounts int, outputFile string, maxWorkers int, proxy, defaultPassword, defaultDomain string) {
var printMu sync.Mutex
var fileMu sync.Mutex
var remaining int64 = int64(totalAccounts)
var successCount int64
var failureCount int64
var attemptNum int64
startTime := time.Now()
var wg sync.WaitGroup
for w := 1; w <= maxWorkers; w++ {
wg.Add(1)
go func(workerID int) {
defer wg.Done()
for {
// Claim a slot before starting work
if atomic.AddInt64(&remaining, -1) < 0 {
// No more slots needed, put it back and exit
atomic.AddInt64(&remaining, 1)
return
}
attempt := atomic.AddInt64(&attemptNum, 1)
tag := fmt.Sprintf("%d/%d", attempt, totalAccounts)
success, emailAddr, errStr := registerOne(workerID, tag, proxy, outputFile, defaultPassword, defaultDomain, &printMu, &fileMu)
if success {
atomic.AddInt64(&successCount, 1)
ts := time.Now().Format("15:04:05")
printMu.Lock()
fmt.Printf("[%s] [W%d] ✓ SUCCESS: %s\n", ts, workerID, emailAddr)
printMu.Unlock()
} else {
atomic.AddInt64(&failureCount, 1)
// Failed — return the slot so it gets retried
atomic.AddInt64(&remaining, 1)
ts := time.Now().Format("15:04:05")
printMu.Lock()
fmt.Printf("[%s] [W%d] ✗ FAILURE: %s | %s\n", ts, workerID, emailAddr, errStr)
printMu.Unlock()
}
}
}(w)
}
wg.Wait()
elapsed := time.Since(startTime)
elapsedStr := formatDuration(elapsed)
fmt.Printf("\n--- Batch Registration Summary ---\n")
fmt.Printf("Target: %d\n", totalAccounts)
fmt.Printf("Success: %d\n", successCount)
fmt.Printf("Attempts: %d\n", attemptNum)
fmt.Printf("Failures: %d\n", failureCount)
fmt.Printf("Elapsed: %s\n", elapsedStr)
fmt.Printf("----------------------------------\n")
}
func formatDuration(d time.Duration) string {
h := int(d.Hours())
m := int(d.Minutes()) % 60
s := int(d.Seconds()) % 60
if h > 0 {
return fmt.Sprintf("%dh %dm %ds", h, m, s)
}
if m > 0 {
return fmt.Sprintf("%dm %ds", m, s)
}
return fmt.Sprintf("%ds", s)
}

126
internal/register/client.go Normal file
View File

@@ -0,0 +1,126 @@
package register
import (
"fmt"
"net/url"
"sync"
"time"
http "github.com/bogdanfinn/fhttp"
"github.com/bogdanfinn/tls-client"
"github.com/google/uuid"
"github.com/verssache/chatgpt-creator/internal/chrome"
)
const (
baseURL = "https://chatgpt.com"
authURL = "https://auth.openai.com"
)
type Client struct {
session tls_client.HttpClient
proxy string
tag string
workerID int
deviceID string
impersonate string
major int
fullVersion string
ua string
secChUA string
printMu *sync.Mutex
fileMu *sync.Mutex
}
func NewClient(proxy, tag string, workerID int, printMu, fileMu *sync.Mutex) (*Client, error) {
profile, fullVersion, ua := chrome.RandomChromeVersion()
impersonate := profile.Impersonate
mappedProfile := chrome.MapToTLSProfile(impersonate)
options := []tls_client.HttpClientOption{
tls_client.WithClientProfile(mappedProfile),
tls_client.WithCookieJar(tls_client.NewCookieJar()),
}
if proxy != "" {
options = append(options, tls_client.WithProxyUrl(proxy))
}
session, err := tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...)
if err != nil {
return nil, fmt.Errorf("failed to create http client: %w", err)
}
deviceID := uuid.New().String()
c := &Client{
session: session,
proxy: proxy,
tag: tag,
workerID: workerID,
deviceID: deviceID,
impersonate: impersonate,
fullVersion: fullVersion,
ua: ua,
printMu: printMu,
fileMu: fileMu,
}
// major version for sec-ch-ua
c.major = profile.Major
c.secChUA = profile.SecChUA
// Add initial cookie
u, _ := url.Parse(baseURL)
cookies := []*http.Cookie{
{
Name: "oai-did",
Value: deviceID,
Domain: "chatgpt.com",
Path: "/",
},
}
session.GetCookieJar().SetCookies(u, cookies)
return c, nil
}
func (c *Client) do(req *http.Request) (*http.Response, error) {
if req.Header.Get("User-Agent") == "" {
req.Header.Set("User-Agent", c.ua)
}
if req.Header.Get("Accept") == "" {
req.Header.Set("Accept", "*/*")
}
if req.Header.Get("Accept-Language") == "" {
req.Header.Set("Accept-Language", "en-US,en;q=0.9")
}
if req.Header.Get("sec-ch-ua") == "" {
req.Header.Set("sec-ch-ua", c.secChUA)
}
if req.Header.Get("sec-ch-ua-mobile") == "" {
req.Header.Set("sec-ch-ua-mobile", "?0")
}
if req.Header.Get("sec-ch-ua-platform") == "" {
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
}
return c.session.Do(req)
}
func (c *Client) log(step string, status int) {
c.printMu.Lock()
defer c.printMu.Unlock()
ts := time.Now().Format("15:04:05")
fmt.Printf("[%s] [W%d] [%s] %s | %d\n", ts, c.workerID, c.tag, step, status)
}
func (c *Client) print(msg string) {
c.printMu.Lock()
defer c.printMu.Unlock()
ts := time.Now().Format("15:04:05")
fmt.Printf("[%s] [W%d] [%s] %s\n", ts, c.workerID, c.tag, msg)
}

412
internal/register/flow.go Normal file
View File

@@ -0,0 +1,412 @@
package register
import (
"encoding/json"
"fmt"
"io"
"math/rand"
"net/url"
"strings"
"time"
http "github.com/bogdanfinn/fhttp"
"github.com/verssache/chatgpt-creator/internal/email"
"github.com/verssache/chatgpt-creator/internal/util"
)
// visitHomepage visits chatgpt.com to initialize session
func (c *Client) visitHomepage() error {
var resp *http.Response
var err error
for retry := 0; retry < 3; retry++ {
req, _ := http.NewRequest("GET", baseURL+"/", nil)
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
req.Header.Set("Upgrade-Insecure-Requests", "1")
resp, err = c.do(req)
if err != nil {
return err
}
c.log(fmt.Sprintf("Visit Homepage (Try %d)", retry+1), resp.StatusCode)
if resp.StatusCode == 200 || resp.StatusCode == 302 || resp.StatusCode == 307 {
resp.Body.Close()
return nil
}
resp.Body.Close()
time.Sleep(1 * time.Second)
}
return fmt.Errorf("failed to visit homepage after 3 retries (status: %d)", resp.StatusCode)
}
// getCSRF retrieves the CSRF token from chatgpt.com
func (c *Client) getCSRF() (string, error) {
req, _ := http.NewRequest("GET", baseURL+"/api/auth/csrf", nil)
req.Header.Set("Accept", "application/json")
req.Header.Set("Referer", baseURL+"/")
resp, err := c.do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var data struct {
CSRFToken string `json:"csrfToken"`
}
if err := json.Unmarshal(body, &data); err != nil {
return "", err
}
c.log("Get CSRF", resp.StatusCode)
if data.CSRFToken == "" {
return "", fmt.Errorf("csrf token not found")
}
return data.CSRFToken, nil
}
// signin initiates the signin process and returns the authorize URL
func (c *Client) signin(email, csrf string) (string, error) {
signinURL := baseURL + "/api/auth/signin/openai"
params := url.Values{}
params.Set("prompt", "login")
params.Set("ext-oai-did", c.deviceID)
params.Set("auth_session_logging_id", util.GenerateUUID()) // Assuming util has this or use google/uuid
params.Set("screen_hint", "login_or_signup")
params.Set("login_hint", email)
fullURL := signinURL + "?" + params.Encode()
formData := url.Values{}
formData.Set("callbackUrl", baseURL+"/")
formData.Set("csrfToken", csrf)
formData.Set("json", "true")
req, _ := http.NewRequest("POST", fullURL, strings.NewReader(formData.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "application/json")
req.Header.Set("Referer", baseURL+"/")
req.Header.Set("Origin", baseURL)
resp, err := c.do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var data struct {
URL string `json:"url"`
}
if err := json.Unmarshal(body, &data); err != nil {
return "", err
}
c.log("Signin", resp.StatusCode)
if data.URL == "" {
return "", fmt.Errorf("authorize url not found")
}
return data.URL, nil
}
// authorize visits the authorize URL and returns the final redirect URL
func (c *Client) authorize(authURL string) (string, error) {
req, _ := http.NewRequest("GET", authURL, nil)
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
req.Header.Set("Referer", baseURL+"/")
req.Header.Set("Upgrade-Insecure-Requests", "1")
resp, err := c.do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
finalURL := resp.Request.URL.String()
c.log("Authorize", resp.StatusCode)
return finalURL, nil
}
// register registers the user with email and password
func (c *Client) register(email, password string) (int, map[string]interface{}, error) {
regURL := authURL + "/api/accounts/user/register"
payload := map[string]string{
"username": email,
"password": password,
}
jsonPayload, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", regURL, strings.NewReader(string(jsonPayload)))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("Referer", authURL+"/create-account/password")
req.Header.Set("Origin", authURL)
// Add trace headers if available in util
traceHeaders := util.MakeTraceHeaders()
for k, v := range traceHeaders {
req.Header.Set(k, v)
}
resp, err := c.do(req)
if err != nil {
return 0, nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var data map[string]interface{}
json.Unmarshal(body, &data)
c.log("Register", resp.StatusCode)
return resp.StatusCode, data, nil
}
// sendOTP sends the OTP to the user's email
func (c *Client) sendOTP() (int, map[string]interface{}, error) {
otpURL := authURL + "/api/accounts/email-otp/send"
req, _ := http.NewRequest("GET", otpURL, nil)
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
req.Header.Set("Referer", authURL+"/create-account/password")
req.Header.Set("Upgrade-Insecure-Requests", "1")
resp, err := c.do(req)
if err != nil {
return 0, nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var data map[string]interface{}
if err := json.Unmarshal(body, &data); err != nil {
data = map[string]interface{}{"text": string(body)}
}
c.log("Send OTP", resp.StatusCode)
return resp.StatusCode, data, nil
}
// validateOTP validates the OTP code
func (c *Client) validateOTP(code string) (int, map[string]interface{}, error) {
valURL := authURL + "/api/accounts/email-otp/validate"
payload := map[string]string{"code": code}
jsonPayload, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", valURL, strings.NewReader(string(jsonPayload)))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("Referer", authURL+"/email-verification")
req.Header.Set("Origin", authURL)
traceHeaders := util.MakeTraceHeaders()
for k, v := range traceHeaders {
req.Header.Set(k, v)
}
resp, err := c.do(req)
if err != nil {
return 0, nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var data map[string]interface{}
json.Unmarshal(body, &data)
c.log(fmt.Sprintf("Validate OTP [%s]", code), resp.StatusCode)
return resp.StatusCode, data, nil
}
// createAccount creates the user account with name and birthdate
func (c *Client) createAccount(name, birthdate string) (int, map[string]interface{}, error) {
createURL := authURL + "/api/accounts/create_account"
payload := map[string]string{
"name": name,
"birthdate": birthdate,
}
jsonPayload, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", createURL, strings.NewReader(string(jsonPayload)))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")
req.Header.Set("Referer", authURL+"/about-you")
req.Header.Set("Origin", authURL)
traceHeaders := util.MakeTraceHeaders()
for k, v := range traceHeaders {
req.Header.Set(k, v)
}
resp, err := c.do(req)
if err != nil {
return 0, nil, err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var data map[string]interface{}
json.Unmarshal(body, &data)
c.log("Create Account", resp.StatusCode)
return resp.StatusCode, data, nil
}
// callback handles the callback URL
func (c *Client) callback(cbURL string) (int, map[string]interface{}, error) {
if cbURL == "" {
return 0, nil, fmt.Errorf("empty callback url")
}
req, _ := http.NewRequest("GET", cbURL, nil)
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
req.Header.Set("Upgrade-Insecure-Requests", "1")
resp, err := c.do(req)
if err != nil {
return 0, nil, err
}
defer resp.Body.Close()
c.log("Callback", resp.StatusCode)
return resp.StatusCode, map[string]interface{}{"final_url": resp.Request.URL.String()}, nil
}
func (c *Client) RunRegister(emailAddr, password, name, birthdate string) error {
c.print("Starting registration flow...")
if err := c.visitHomepage(); err != nil {
return err
}
c.randomDelay(0.3, 0.8)
csrf, err := c.getCSRF()
if err != nil {
return err
}
c.randomDelay(0.2, 0.5)
authURL, err := c.signin(emailAddr, csrf)
if err != nil {
return err
}
c.randomDelay(0.3, 0.8)
finalURL, err := c.authorize(authURL)
if err != nil {
return err
}
c.randomDelay(0.3, 0.8)
u, _ := url.Parse(finalURL)
finalPath := u.Path
needOTP := false
if strings.Contains(finalPath, "create-account/password") {
c.randomDelay(0.5, 1.0)
status, data, err := c.register(emailAddr, password)
if err != nil {
return err
}
if status != 200 {
return fmt.Errorf("register failed (%d): %v", status, data)
}
c.randomDelay(0.3, 0.8)
c.sendOTP()
needOTP = true
} else if strings.Contains(finalPath, "email-verification") || strings.Contains(finalPath, "email-otp") {
c.print("Jump to OTP verification stage")
needOTP = true
} else if strings.Contains(finalPath, "about-you") {
c.print("Jump to fill information stage")
c.randomDelay(0.5, 1.0)
status, data, err := c.createAccount(name, birthdate)
if err != nil {
return err
}
if status != 200 {
return fmt.Errorf("create account failed (%d): %v", status, data)
}
c.randomDelay(0.3, 0.5)
var cbURL string
if u, ok := data["continue_url"].(string); ok {
cbURL = u
} else if u, ok := data["url"].(string); ok {
cbURL = u
} else if u, ok := data["redirect_url"].(string); ok {
cbURL = u
}
c.callback(cbURL)
return nil
} else if strings.Contains(finalPath, "callback") || strings.Contains(finalURL, "chatgpt.com") {
c.print("Account registration completed")
return nil
} else {
c.print(fmt.Sprintf("Unknown jump: %s", finalURL))
c.register(emailAddr, password)
c.sendOTP()
needOTP = true
}
if needOTP {
otpCode, err := email.GetVerificationCode(emailAddr, 20, 3*time.Second)
if err != nil {
return err
}
c.randomDelay(0.3, 0.8)
status, data, err := c.validateOTP(otpCode)
if err != nil {
return err
}
if status != 200 {
c.print("Verification code failed, retrying...")
c.sendOTP()
c.randomDelay(1.0, 2.0)
otpCode, err = email.GetVerificationCode(emailAddr, 10, 3*time.Second)
if err != nil {
return err
}
c.randomDelay(0.3, 0.8)
status, data, err = c.validateOTP(otpCode)
if err != nil {
return err
}
if status != 200 {
return fmt.Errorf("verification code failed after retry (%d): %v", status, data)
}
}
}
c.randomDelay(0.5, 1.5)
status, data, err := c.createAccount(name, birthdate)
if err != nil {
return err
}
if status != 200 {
return fmt.Errorf("create account failed (%d): %v", status, data)
}
c.randomDelay(0.2, 0.5)
var cbURL string
if u, ok := data["continue_url"].(string); ok {
cbURL = u
} else if u, ok := data["url"].(string); ok {
cbURL = u
} else if u, ok := data["redirect_url"].(string); ok {
cbURL = u
}
c.callback(cbURL)
return nil
}
func (c *Client) randomDelay(low, high float64) {
delay := low + rand.Float64()*(high-low)
time.Sleep(time.Duration(delay * float64(time.Second)))
}

24
internal/util/helpers.go Normal file
View File

@@ -0,0 +1,24 @@
package util
import (
"math/rand"
"time"
"github.com/google/uuid"
)
const alphanumeric = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
// RandStr generates a random alphanumeric string of given length.
func RandStr(length int) string {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
b := make([]byte, length)
for i := range b {
b[i] = alphanumeric[r.Intn(len(alphanumeric))]
}
return string(b)
}
// GenerateUUID generates a random UUID.
func GenerateUUID() string {
return uuid.New().String()
}

23
internal/util/names.go Normal file
View File

@@ -0,0 +1,23 @@
package util
import (
"fmt"
"math/rand"
"time"
"github.com/brianvoe/gofakeit/v7"
)
// RandomName returns a random first and last name using gofakeit.
func RandomName() (string, string) {
return gofakeit.FirstName(), gofakeit.LastName()
}
// RandomBirthdate returns a random birthdate string in YYYY-MM-DD format from 1985-2002.
func RandomBirthdate() string {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
year := r.Intn(2002-1985+1) + 1985
month := r.Intn(12) + 1
day := r.Intn(28) + 1
return fmt.Sprintf("%04d-%02d-%02d", year, month, day)
}

40
internal/util/password.go Normal file
View File

@@ -0,0 +1,40 @@
package util
import (
"math/rand"
"time"
)
const (
lowerChars = "abcdefghijklmnopqrstuvwxyz"
upperChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
digitChars = "0123456789"
specialChars = "!@#$%&*"
allChars = lowerChars + upperChars + digitChars + specialChars
)
// GeneratePassword generates a random password of given length (default 14).
// It guarantees at least 1 lowercase, 1 uppercase, 1 digit, and 1 special character.
func GeneratePassword(length int) string {
if length <= 0 {
length = 14
}
r := rand.New(rand.NewSource(time.Now().UnixNano()))
password := make([]byte, length)
password[0] = lowerChars[r.Intn(len(lowerChars))]
password[1] = upperChars[r.Intn(len(upperChars))]
password[2] = digitChars[r.Intn(len(digitChars))]
password[3] = specialChars[r.Intn(len(specialChars))]
for i := 4; i < length; i++ {
password[i] = allChars[r.Intn(len(allChars))]
}
r.Shuffle(len(password), func(i, j int) {
password[i], password[j] = password[j], password[i]
})
return string(password)
}

38
internal/util/trace.go Normal file
View File

@@ -0,0 +1,38 @@
package util
import (
"crypto/rand"
"encoding/hex"
"fmt"
"math/big"
)
// MakeTraceHeaders generates Datadog-compatible trace headers.
func MakeTraceHeaders() map[string]string {
traceID := make([]byte, 16)
rand.Read(traceID)
spanID := make([]byte, 8)
rand.Read(spanID)
traceIDHex := hex.EncodeToString(traceID)
spanIDHex := hex.EncodeToString(spanID)
// traceparent: 00-{traceID}-{spanID}-01
traceparent := fmt.Sprintf("00-%s-%s-01", traceIDHex, spanIDHex)
// tracestate: dd=t.dm:-1;t.tid:{first16ofTraceID};s:-1
tracestate := fmt.Sprintf("dd=t.dm:-1;t.tid:%s;s:-1", traceIDHex[:16])
// x-datadog-trace-id: decimal conversion of last 16 hex chars (last 8 bytes)
traceIDInt := new(big.Int).SetBytes(traceID[8:])
// x-datadog-parent-id: decimal conversion of spanID
spanIDInt := new(big.Int).SetBytes(spanID)
return map[string]string{
"traceparent": traceparent,
"tracestate": tracestate,
"x-datadog-trace-id": traceIDInt.String(),
"x-datadog-parent-id": spanIDInt.String(),
"x-datadog-sampling-priority": "-1",
}
}