mirror of
https://github.com/verssache/chatgpt-creator.git
synced 2026-05-16 21:59:33 +00:00
Initial commit: ChatGPT Account Registration Bot
This commit is contained in:
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Binary output
|
||||||
|
chatgpt-creator
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# Config and Results
|
||||||
|
results.txt
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal 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
131
README.md
Normal 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
124
cmd/register/main.go
Normal 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
6
config.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"proxy": "",
|
||||||
|
"output_file": "results.txt",
|
||||||
|
"default_password": "",
|
||||||
|
"default_domain": ""
|
||||||
|
}
|
||||||
28
go.mod
Normal file
28
go.mod
Normal 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
118
go.sum
Normal 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=
|
||||||
48
internal/chrome/profiles.go
Normal file
48
internal/chrome/profiles.go
Normal 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
59
internal/config/config.go
Normal 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
153
internal/email/generator.go
Normal 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
134
internal/register/batch.go
Normal 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
126
internal/register/client.go
Normal 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
412
internal/register/flow.go
Normal 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
24
internal/util/helpers.go
Normal 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
23
internal/util/names.go
Normal 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
40
internal/util/password.go
Normal 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
38
internal/util/trace.go
Normal 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",
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user