Files
xiaolajiaoyyds 6f71df4849 Initial open-source release
多平台账号批量注册系统,支持 OpenAI / Grok / Kiro / Gemini 平台的全自动注册。

Features:
- 用户名/密码注册登录(首个用户自动管理员)
- JWT 认证 + bcrypt 密码加密
- 积分系统与多用户管理
- HuggingFace Space 弹性 Worker 节点管理
- Cloudflare Worker 负载均衡
- 实时日志推送(SSE/WebSocket)
- 管理后台(用户管理、系统设置、代理池、卡池)

Tech Stack: Go 1.25 / Gin / GORM / PostgreSQL / Vue 3 / Vite / TailwindCSS
2026-03-22 03:44:42 +08:00

457 lines
14 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package worker
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"math/rand"
"net/http"
"net/url"
"os"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/xiaolajiaoyyds/regplatformm/internal/pkg/grpcweb"
"github.com/xiaolajiaoyyds/regplatformm/internal/pkg/tempmail"
"github.com/xiaolajiaoyyds/regplatformm/internal/pkg/turnstile"
)
// grokRegRequest 远程 Grok 注册请求(HF 端接收)
type grokRegRequest struct {
Proxy string `json:"proxy"`
YYDSMailURL string `json:"yydsmail_url"`
YYDSMailKey string `json:"yydsmail_key"`
EmailPriority string `json:"email_priority"`
TurnstileSolverURL string `json:"turnstile_solver_url"`
TurnstileSolverProxy string `json:"turnstile_solver_proxy"`
CFBypassSolverURL string `json:"cf_bypass_solver_url"`
CapSolverKey string `json:"capsolver_key"`
YesCaptchaKey string `json:"yescaptcha_key"`
SiteKey string `json:"site_key"`
ActionID string `json:"action_id"`
StateTree string `json:"state_tree"`
}
// GrokProtocolRegisterHandler 完整 Grok 注册端点(HF Space 独立运行)
// POST /api/v1/process
// 供远程调用:日本后端 → CF Workers → HF Space
// 流程: 创建邮箱 → gRPC-web 发/收验证码 → Turnstile → Server Action → SSO → NSFW
func GrokProtocolRegisterHandler(c *gin.Context) {
var req grokRegRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"ok": false, "error": "invalid request: " + err.Error()})
return
}
// 构建 multi-provider 邮箱配置
priority := req.EmailPriority
if priority == "" {
priority = "yydsmail"
}
cfg := map[string]string{
"yydsmail_api_key": req.YYDSMailKey,
"yydsmail_base_url": req.YYDSMailURL,
"email_provider_priority": priority,
}
mailProvider := tempmail.NewMultiProvider(cfg)
// 日志收集
logs := make([]string, 0, 30)
logf := func(format string, args ...interface{}) {
msg := fmt.Sprintf(format, args...)
logs = append(logs, msg)
fmt.Printf("[LOG] %s\n", msg) // 同时打印到 stdout,方便调试
}
// 诊断日志:记录收到的配置
logf("[*] 邮箱优先级: %s", priority)
creds := ""
if req.YYDSMailKey != "" {
creds += "yydsmail "
}
if creds == "" {
creds = "(未配置凭证)"
}
logf("[*] 可用凭证: %s", creds)
if req.Proxy != "" {
logf("[*] 代理: %s", req.Proxy)
}
// 排队等待信息(由 ConcurrencyLimiter 中间件注入 gin context)
if waited, ok := c.Get("queue_waited"); ok && waited.(bool) {
pos, _ := c.Get("queue_position")
waitSec, _ := c.Get("queue_wait_seconds")
logf("[*] 排队等待完成: 位置 #%d, 等待 %d 秒", pos, waitSec)
}
// 构建代理
var proxy *ProxyEntry
if req.Proxy != "" {
proxy = &ProxyEntry{HTTPS: req.Proxy, HTTP: req.Proxy}
}
// 执行上下文(5 分钟超时,覆盖 Turnstile + 验证码轮询 + 注册 + NSFW)
ctx, cancel := context.WithTimeout(c.Request.Context(), 5*time.Minute)
defer cancel()
w := &GrokWorker{}
profile := browserProfiles[rand.Intn(len(browserProfiles))]
logf("[*] 任务开始")
// ─── 创建 HTTP 会话 ───
client := w.makeHTTPClient(proxy, 30*time.Second)
// 会话预热(获取 __cf_bm cookie)
warmupReq, _ := http.NewRequestWithContext(ctx, "GET", "https://accounts.x.ai", nil)
warmupReq.Header.Set("User-Agent", profile.UserAgent)
if resp, err := client.Do(warmupReq); err == nil {
resp.Body.Close()
}
// ─── Phase 0: 配置扫描 + Solver 初始化(在邮箱创建之前,避免浪费邮箱) ───
// Turnstile solver 配置(优先请求参数,Mac Mini 环境下回退到容器环境变量)
var solverURLs []string
if req.TurnstileSolverURL != "" {
solverURLs = append(solverURLs, req.TurnstileSolverURL)
} else if envURL := os.Getenv("TURNSTILE_SOLVER_URL"); envURL != "" {
solverURLs = append(solverURLs, envURL)
logf("[*] Turnstile solver: %s (环境变量)", envURL)
}
if req.CFBypassSolverURL != "" {
solverURLs = append(solverURLs, req.CFBypassSolverURL)
} else if envURL := os.Getenv("CF_BYPASS_SOLVER_URL"); envURL != "" {
solverURLs = append(solverURLs, envURL)
logf("[*] CF bypass solver: %s (环境变量)", envURL)
}
// Solver 专用代理:优先用面板配的 turnstile_solver_proxy,没有才用注册代理
var solverProxyURL string
if req.TurnstileSolverProxy != "" {
solverProxyURL = req.TurnstileSolverProxy
} else if proxy != nil {
if proxy.HTTPS != "" {
solverProxyURL = proxy.HTTPS
} else if proxy.HTTP != "" {
solverProxyURL = proxy.HTTP
}
}
solver := turnstile.NewSolver(solverURLs, req.CapSolverKey, req.YesCaptchaKey, solverProxyURL)
siteKey := req.SiteKey
if siteKey == "" {
siteKey = "0x4AAAAAAAhr9JGVDZbrZOo0" // 默认 fallback
}
actionID := req.ActionID
stateTree := req.StateTree
if stateTree == "" {
stateTree = defaultStateTree
}
// action_id 为空时自行扫描(HF Space 可直连 x.ai)
if actionID == "" {
logf("[*] 注册配置未提供,自行扫描获取...")
scanner := &GrokWorker{}
scanned, scanErr := scanner.doScan(ctx, proxy)
if scanErr == nil {
if aid := scanned["action_id"]; aid != "" {
actionID = aid
logf("[+] 注册配置已获取")
}
if sk := scanned["site_key"]; sk != "" {
siteKey = sk
}
if st := scanned["state_tree"]; st != "" {
stateTree = st
}
} else {
logf("[-] 自行扫描失败: %s", scanErr)
}
}
if actionID == "" {
logf("[-] 缺少注册配置且扫描失败,无法注册")
c.JSON(http.StatusOK, gin.H{"ok": false, "error": "missing configuration", "logs": logs})
return
}
// ─── Phase 1: 创建临时邮箱 ───
email, mailMeta, err := mailProvider.GenerateEmail(ctx)
if err != nil {
logf("[-] 创建邮箱失败: %s", err)
c.JSON(http.StatusOK, gin.H{"ok": false, "error": "initialization failed: " + err.Error(), "logs": logs})
return
}
password := randomString(15, "abcdefghijklmnopqrstuvwxyz0123456789")
logf("[*] 邮箱: %s", email)
// 注册失败时清理邮箱
defer func() {
go mailProvider.DeleteEmail(context.Background(), email, mailMeta)
}()
// ─── Phase 2: 发送验证码 (gRPC-web) ───
logf("[*] 发送验证码...")
grpcBody := grpcweb.EncodeEmailCode(email)
sendResp, err := w.doGRPCWeb(ctx, client, profile.UserAgent,
"https://accounts.x.ai/auth_mgmt.AuthManagement/CreateEmailValidationCode",
"https://accounts.x.ai", "https://accounts.x.ai/sign-up?redirect=grok-com",
grpcBody, nil)
if err != nil || sendResp == nil || sendResp.StatusCode != 200 {
status := 0
if sendResp != nil {
status = sendResp.StatusCode
sendResp.Body.Close()
}
logf("[-] 发送验证码失败: HTTP %d, %v", status, err)
c.JSON(http.StatusOK, gin.H{"ok": false, "error": "verification failed", "email": email, "logs": logs})
return
}
sendResp.Body.Close()
logf("[+] 验证码已发送")
// ─── Phase 3: 获取验证码 ───
logf("[*] 等待验证码(最长 45s)...")
var code string
for attempt := 1; attempt <= 45; attempt++ {
select {
case <-ctx.Done():
logf("[-] 等待验证码时任务被取消")
c.JSON(http.StatusOK, gin.H{"ok": false, "error": "cancelled", "email": email, "logs": logs})
return
case <-time.After(1 * time.Second):
}
c2, err := mailProvider.FetchVerificationCode(ctx, email, mailMeta, 1, 0)
if err == nil && c2 != "" {
code = c2
break
}
if attempt%5 == 0 {
logf("[*] 等待验证码中... 已等待 %ds", attempt)
}
}
if code == "" {
logf("[-] 获取验证码超时")
tempmail.RecordFailure(mailMeta["provider"])
c.JSON(http.StatusOK, gin.H{
"ok": false, "error": "verification code timeout",
"email": email, "provider": mailMeta["provider"], "logs": logs,
})
return
}
logf("[+] 验证码: %s", code)
tempmail.RecordSuccess(mailMeta["provider"])
// ─── Phase 4: 验证邮箱 (gRPC-web) ───
verifyBody := grpcweb.EncodeVerifyCode(email, code)
verifyResp, err := w.doGRPCWeb(ctx, client, profile.UserAgent,
"https://accounts.x.ai/auth_mgmt.AuthManagement/VerifyEmailValidationCode",
"https://accounts.x.ai", "https://accounts.x.ai/sign-up?redirect=grok-com",
verifyBody, nil)
if err != nil || verifyResp == nil || verifyResp.StatusCode != 200 {
if verifyResp != nil {
verifyResp.Body.Close()
}
logf("[-] 验证邮箱失败")
c.JSON(http.StatusOK, gin.H{"ok": false, "error": "validation failed", "email": email, "logs": logs})
return
}
verifyResp.Body.Close()
logf("[+] 邮箱验证成功")
// ─── Phase 5+6: Turnstile + Server Actions 注册 ───
firstName := randomName(4, 6)
lastName := randomName(4, 6)
var ssoToken string
registered := false
for attempt := 0; attempt < 3; attempt++ {
select {
case <-ctx.Done():
c.JSON(http.StatusOK, gin.H{"ok": false, "error": "timeout", "email": email, "logs": logs})
return
default:
}
logf("[*] 注册尝试 %d/3...", attempt+1)
// Turnstile 求解
logf("[*] 验证远程服务接口...")
turnstileToken, err := solver.Solve(ctx, "https://accounts.x.ai/sign-up", siteKey, 3, logf)
if err != nil {
logf("[-] 验证失败: %s", err)
ctxSleep(ctx, 3*time.Second)
continue
}
logf("[+] 卧槽Σ(°ロ°)验证通过 (%d chars)", len(turnstileToken))
// 用同一个 Turnstile token 提交注册,404 时重新扫描 action_id 并立即重试(最多 2 次)
var respText string
regOK := false
for regRetry := 0; regRetry < 2; regRetry++ {
regPayload := []map[string]interface{}{{
"emailValidationCode": code,
"createUserAndSessionRequest": map[string]interface{}{
"email": email,
"givenName": firstName,
"familyName": lastName,
"clearTextPassword": password,
"tosAcceptedVersion": "$undefined",
},
"turnstileToken": turnstileToken,
"promptOnDuplicateEmail": true,
}}
jsonBody, _ := json.Marshal(regPayload)
regReq, _ := http.NewRequestWithContext(ctx, "POST", "https://accounts.x.ai/sign-up", bytes.NewReader(jsonBody))
regReq.Header.Set("Content-Type", "text/plain;charset=UTF-8")
regReq.Header.Set("Accept", "text/x-component")
regReq.Header.Set("Next-Action", actionID)
regReq.Header.Set("Next-Router-State-Tree", stateTree)
regReq.Header.Set("Origin", "https://accounts.x.ai")
regReq.Header.Set("Referer", "https://accounts.x.ai/sign-up")
regReq.Header.Set("User-Agent", profile.UserAgent)
regResp, err := client.Do(regReq)
if err != nil {
logf("[-] 注册请求失败: %s", err)
break
}
respBody, _ := io.ReadAll(regResp.Body)
regResp.Body.Close()
respText = string(respBody)
if regResp.StatusCode == 200 {
regOK = true
break
}
logf("[-] 注册响应: HTTP %d", regResp.StatusCode)
// action_id 过期 → 重新扫描刷新,用同一个 token 立即重试
if regResp.StatusCode == 404 && strings.Contains(respText, "action not found") {
logf("[*] 注册配置已过期,重新扫描...")
scanner := &GrokWorker{}
if scanned, scanErr := scanner.doScan(ctx, proxy); scanErr == nil {
if aid := scanned["action_id"]; aid != "" {
actionID = aid
logf("[+] 注册配置已刷新,用同一个 token 立即重试")
}
if st := scanned["state_tree"]; st != "" {
stateTree = st
}
ctxSleep(ctx, 1*time.Second)
continue // 内层循环重试,不重新求解 Turnstile
} else {
logf("[-] 重新扫描失败: %s", scanErr)
}
}
break // 非 404 错误直接退出内层循环
}
if !regOK {
ctxSleep(ctx, 3*time.Second)
continue
}
// 提取 set-cookie URL
var cookieURLMatch string
if sm := reCookieURLAnchored.FindStringSubmatch(respText); len(sm) > 1 {
cookieURLMatch = sm[1]
} else if sm := reCookieURLQuoted.FindStringSubmatch(respText); len(sm) > 1 {
cookieURLMatch = sm[1]
}
if cookieURLMatch == "" {
logf("[-] 会话处理失败,响应异常")
ctxSleep(ctx, 3*time.Second)
continue
}
// 请求 set-cookie URL 获取 SSO token
cookieReq, _ := http.NewRequestWithContext(ctx, "GET", cookieURLMatch, nil)
cookieReq.Header.Set("User-Agent", profile.UserAgent)
cookieResp, err := client.Do(cookieReq)
if err != nil {
logf("[-] 会话回调失败: %s", err)
break
}
for _, sc := range cookieResp.Header["Set-Cookie"] {
if strings.HasPrefix(sc, "sso=") && !strings.HasPrefix(sc, "sso-rw=") {
parts := strings.SplitN(sc, ";", 2)
kv := strings.SplitN(parts[0], "=", 2)
if len(kv) == 2 && kv[1] != "" {
ssoToken = kv[1]
}
}
}
cookieResp.Body.Close()
// 降级: cookie jar 里找 sso
if ssoToken == "" {
for _, domain := range []string{
cookieResp.Request.URL.String(),
"https://accounts.x.ai",
"https://grok.com",
"https://x.ai",
} {
domainURL, err := url.Parse(domain)
if err != nil {
continue
}
for _, ck := range client.Jar.Cookies(domainURL) {
if ck.Name == "sso" && ssoToken == "" {
ssoToken = ck.Value
}
}
}
}
if ssoToken == "" {
logf("[-] 未获取到登录凭证")
break
}
logf("[+] 注册成功!")
registered = true
break
}
if !registered {
c.JSON(http.StatusOK, gin.H{"ok": false, "error": "process failed", "email": email, "logs": logs})
return
}
// ─── Phase 7+8: TOS + 生日 + NSFW + Unhinged + PayUrl ───
// HF 上只用 enableNSFWGo(纯 Go),不带 Python fallback
logf("[*] 马上处理..设置中...")
nsfwEnabled, checkoutURL := w.enableNSFWGo(ctx, ssoToken, email, proxy, logf)
if nsfwEnabled {
logf("[+] 马上处理..成功啦")
} else {
logf("[!] 马上处理..失败(账号已创建,继续返回)")
}
logf("[OK] 任务完成: %s", email)
// 注入邮箱元数据,供后续 OTP 查询使用
emailMetaCopy := make(map[string]interface{}, len(mailMeta))
for k, v := range mailMeta {
emailMetaCopy[k] = v
}
result := gin.H{
"ok": true,
"email": email,
"auth_token": ssoToken,
"feature_enabled": nsfwEnabled,
"provider": mailMeta["provider"],
"email_meta": emailMetaCopy,
"logs": logs,
}
if checkoutURL != "" {
result["redirect_url"] = checkoutURL
}
c.JSON(http.StatusOK, result)
}