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

8
.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
# Binary output
chatgpt-creator
# OS files
.DS_Store
# Config and Results
results.txt

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 ChatGPT Account Creator
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

131
README.md Normal file
View File

@@ -0,0 +1,131 @@
# ChatGPT Account Registration Bot
Automated bulk ChatGPT account registration bot built with Go. Features concurrent workers, TLS fingerprint spoofing, automatic email generation, OTP verification, and retry-until-success logic.
## Features
- **Concurrent Registration** — Configurable worker pool for parallel account creation
- **TLS Fingerprinting** — Randomized Chrome TLS profiles to avoid detection
- **Auto Email Generation** — Generates temporary emails via [generator.email](https://generator.email) or custom domains
- **OTP Verification** — Automatic email OTP retrieval and validation
- **Retry Loop** — Automatically retries failed registrations until target count is reached
- **Proxy Support** — Optional HTTP/SOCKS proxy for all requests
- **Configurable** — JSON config file with interactive prompt overrides
## Requirements
- Go 1.21+
## Installation
```bash
git clone https://github.com/verssache/chatgpt-creator.git
cd chatgpt-creator
go mod download
```
## Usage
```bash
go run cmd/register/main.go
```
### Interactive Prompts
```
Proxy (enter to skip):
Total accounts to register: 5
Max concurrent workers (default: 3): 2
Default password (current: (random), press Enter to use, or enter new):
Default domain (current: (random from generator.email), press Enter to use, or enter new):
```
### Example Output
```
[22:43:08] [W1] [1/5] Starting registration flow...
[22:43:09] [W1] [1/5] Visit Homepage (Try 1) | 200
[22:43:09] [W1] [1/5] Get CSRF | 200
[22:43:10] [W1] [1/5] Signin | 200
[22:43:12] [W1] [1/5] Authorize | 200
[22:43:15] [W1] [1/5] Register | 200
[22:43:17] [W1] [1/5] Send OTP | 200
[22:43:19] [W1] [1/5] Validate OTP [483291] | 200
[22:43:24] [W1] [1/5] Create Account | 200
[22:43:33] [W1] [1/5] Callback | 200
[22:43:33] [W1] SUCCESS: johndoe8x2kq@smartmail.de
--- Batch Registration Summary ---
Target: 5
Success: 5
Attempts: 6
Failures: 1
Elapsed: 1m 45s
----------------------------------
```
## Configuration
Create a `config.json` in the project root (optional):
```json
{
"proxy": "",
"output_file": "results.txt",
"default_password": "",
"default_domain": ""
}
```
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `proxy` | string | `""` | HTTP/SOCKS proxy URL. Leave empty for direct connection |
| `output_file` | string | `results.txt` | File path for saving registered accounts |
| `default_password` | string | `""` | Password for all accounts. Must be 12+ chars. Empty = random |
| `default_domain` | string | `""` | Email domain to use. Empty = random from generator.email |
Environment variable `PROXY` overrides the config file proxy value.
## Output Format
Registered accounts are saved to the output file in the format:
```
email|password
```
## Project Structure
```
.
├── cmd/
│ └── register/
│ └── main.go # Entry point, interactive prompts
├── internal/
│ ├── config/
│ │ └── config.go # Configuration loading & validation
│ ├── register/
│ │ ├── batch.go # Batch orchestration, worker pool, retry logic
│ │ ├── client.go # HTTP client with TLS fingerprinting
│ │ └── flow.go # Registration flow (CSRF → signup → OTP → callback)
│ ├── email/
│ │ └── generator.go # Temporary email generation
│ ├── chrome/
│ │ └── profiles.go # Chrome TLS profile randomization
│ └── util/
│ ├── helpers.go # Utility functions
│ ├── names.go # Random name generation (gofakeit)
│ ├── password.go # Random password generation
│ └── trace.go # Datadog trace headers
├── config.json # Configuration file
├── go.mod
└── go.sum
```
## Disclaimer
This tool is provided for educational and research purposes only. Use of this tool to create accounts in violation of OpenAI's Terms of Service is solely at your own risk. The author assumes no responsibility for any misuse or consequences arising from the use of this software.
## License
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.

124
cmd/register/main.go Normal file
View File

@@ -0,0 +1,124 @@
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"github.com/verssache/chatgpt-creator/internal/config"
"github.com/verssache/chatgpt-creator/internal/register"
)
func main() {
printBanner()
// Load config
cfg, err := config.Load("config.json")
if err != nil {
fmt.Printf("Error loading config: %v\n", err)
os.Exit(1)
}
reader := bufio.NewReader(os.Stdin)
// 1. Proxy prompt
proxy := cfg.Proxy
if cfg.Proxy == "" {
fmt.Printf("Proxy (enter to skip): ")
proxyInput, _ := reader.ReadString('\n')
proxy = strings.TrimSpace(proxyInput)
}
// 2. Total accounts prompt (required)
fmt.Printf("Total accounts to register: ")
totalInput, _ := reader.ReadString('\n')
totalInput = strings.TrimSpace(totalInput)
if totalInput == "" {
fmt.Println("Error: total accounts is required.")
os.Exit(1)
}
totalAccounts, err := strconv.Atoi(totalInput)
if err != nil {
fmt.Printf("Error: invalid number '%s'.\n", totalInput)
os.Exit(1)
}
// 3. Max workers prompt
defaultWorkers := 3
fmt.Printf("Max concurrent workers (default: %d): ", defaultWorkers)
workersInput, _ := reader.ReadString('\n')
workersInput = strings.TrimSpace(workersInput)
maxWorkers := defaultWorkers
if workersInput != "" {
if val, err := strconv.Atoi(workersInput); err == nil {
maxWorkers = val
}
}
// 4. Default password prompt
defaultPassword := cfg.DefaultPassword
if cfg.DefaultPassword == "" {
fmt.Printf("Default password (current: (random), press Enter to use, or enter new): ")
pwInput, _ := reader.ReadString('\n')
pwInput = strings.TrimSpace(pwInput)
if pwInput != "" {
if len(pwInput) < 12 {
fmt.Println("Error: password must be at least 12 characters.")
os.Exit(1)
}
defaultPassword = pwInput
}
}
// 5. Default domain prompt
defaultDomain := cfg.DefaultDomain
if cfg.DefaultDomain == "" {
fmt.Printf("Default domain (current: (random from generator.email), press Enter to use, or enter new): ")
domainInput, _ := reader.ReadString('\n')
domainInput = strings.TrimSpace(domainInput)
if domainInput != "" {
defaultDomain = domainInput
}
}
fmt.Println()
fmt.Println("-------------------------------------------")
fmt.Printf("Configuration:\n")
fmt.Printf(" Proxy: %s\n", proxy)
fmt.Printf(" Total Accounts: %d\n", totalAccounts)
fmt.Printf(" Max Workers: %d\n", maxWorkers)
if defaultPassword != "" {
fmt.Printf(" Password: %s\n", defaultPassword)
} else {
fmt.Printf(" Password: (random)\n")
}
if defaultDomain != "" {
fmt.Printf(" Domain: %s\n", defaultDomain)
} else {
fmt.Printf(" Domain: (random)\n")
}
fmt.Printf(" Output File: %s\n", cfg.OutputFile)
fmt.Println("-------------------------------------------")
fmt.Println()
// Run the batch
register.RunBatch(totalAccounts, cfg.OutputFile, maxWorkers, proxy, defaultPassword, defaultDomain)
}
func printBanner() {
banner := `
_____ _ _ _____ _____ _______
/ ____| | | | / ____| __ \__ __|
| | | |__ __ _| |_| | __| |__) | | |
| | | '_ \ / _` + "`" + ` | __| | |_ | ___/ | |
| |____| | | | (_| | |_| |__| | | | |
\_____|_| |_|\__,_|\__|\_____|_| |_|
ChatGPT Account Registration Bot
by @verssache
`
fmt.Println(banner)
}

6
config.json Normal file
View File

@@ -0,0 +1,6 @@
{
"proxy": "",
"output_file": "results.txt",
"default_password": "",
"default_domain": ""
}

28
go.mod Normal file
View File

@@ -0,0 +1,28 @@
module github.com/verssache/chatgpt-creator
go 1.25.5
require (
github.com/PuerkitoBio/goquery v1.11.0
github.com/bogdanfinn/fhttp v0.6.8
github.com/bogdanfinn/tls-client v1.14.0
github.com/brianvoe/gofakeit/v7 v7.14.1
github.com/google/uuid v1.6.0
)
require (
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/andybalholm/cascadia v1.3.3 // indirect
github.com/bdandy/go-errors v1.2.2 // indirect
github.com/bdandy/go-socks4 v1.2.3 // indirect
github.com/bogdanfinn/quic-go-utls v1.0.9-utls // indirect
github.com/bogdanfinn/utls v1.7.7-barnius // indirect
github.com/bogdanfinn/websocket v1.5.5-barnius // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
)

118
go.sum Normal file
View File

@@ -0,0 +1,118 @@
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
github.com/bdandy/go-errors v1.2.2 h1:WdFv/oukjTJCLa79UfkGmwX7ZxONAihKu4V0mLIs11Q=
github.com/bdandy/go-errors v1.2.2/go.mod h1:NkYHl4Fey9oRRdbB1CoC6e84tuqQHiqrOcZpqFEkBxM=
github.com/bdandy/go-socks4 v1.2.3 h1:Q6Y2heY1GRjCtHbmlKfnwrKVU/k81LS8mRGLRlmDlic=
github.com/bdandy/go-socks4 v1.2.3/go.mod h1:98kiVFgpdogR8aIGLWLvjDVZ8XcKPsSI/ypGrO+bqHI=
github.com/bogdanfinn/fhttp v0.6.8 h1:LiQyHOY3i0QoxxNB7nq27/nGNNbtPj0fuBPozhR7Ws4=
github.com/bogdanfinn/fhttp v0.6.8/go.mod h1:A+EKDzMx2hb4IUbMx4TlkoHnaJEiLl8r/1Ss1Y+5e5M=
github.com/bogdanfinn/quic-go-utls v1.0.9-utls h1:tV6eDEiRbRCcepALSzxR94JUVD3N3ACIiRLgyc2Ep8s=
github.com/bogdanfinn/quic-go-utls v1.0.9-utls/go.mod h1:aHph9B9H9yPOt5xnhWKSOum27DJAqpiHzwX+gjvaXcg=
github.com/bogdanfinn/tls-client v1.14.0 h1:vyk7Cn4BIvLAGVuMfb0tP22OqogfO1lYamquQNEZU1A=
github.com/bogdanfinn/tls-client v1.14.0/go.mod h1:LsU6mXVn8MOFDwTkyRfI7V1BZM1p0wf2ZfZsICW/1fM=
github.com/bogdanfinn/utls v1.7.7-barnius h1:OuJ497cc7F3yKNVHRsYPQdGggmk5x6+V5ZlrCR7fOLU=
github.com/bogdanfinn/utls v1.7.7-barnius/go.mod h1:aAK1VZQlpKZClF1WEQeq6kyclbkPq4hz6xTbB5xSlmg=
github.com/bogdanfinn/websocket v1.5.5-barnius h1:bY+qnxpai1qe7Jmjx+Sds/cmOSpuuLoR8x61rWltjOI=
github.com/bogdanfinn/websocket v1.5.5-barnius/go.mod h1:gvvEw6pTKHb7yOiFvIfAFTStQWyrm25BMVCTj5wRSsI=
github.com/brianvoe/gofakeit/v7 v7.14.1 h1:a7fe3fonbj0cW3wgl5VwIKfZtiH9C3cLnwcIXWT7sow=
github.com/brianvoe/gofakeit/v7 v7.14.1/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5 h1:YqAladjX7xpA6BM04leXMWAEjS0mTZ5kUU9KRBriQJc=
github.com/tam7t/hpkp v0.0.0-20160821193359-2b70b4024ed5/go.mod h1:2JjD2zLQYH5HO74y5+aE3remJQvl6q4Sn6aWA2wD1Ng=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211104170005-ce137452f963/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

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",
}
}