Files
grok-keygen/auto_register.py
T
chaos 6f7c949681 Replace repository with grok-reg-protocol-cpa project
Overwrite previous grok-keygen contents with the local registration/CPA protocol codebase (secrets and runtime data excluded).
2026-07-11 17:25:37 +08:00

778 lines
31 KiB
Python
Executable File
Raw 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.
#!/usr/bin/env python3
"""
自动注册脚本 v2(Cloudflare catch-all + Gmail IMAP)
流程:生成随机别名 -> Chromium注册 -> 直接IMAP取验证码 -> 填码完成注册
"""
import argparse
import email as email_lib
import imaplib
import json
import os
import random
import re
import string
import sys
import time
from datetime import datetime, timezone
from email.header import decode_header as _decode_header
from email.utils import parsedate_to_datetime
from pathlib import Path
from DrissionPage import Chromium, ChromiumOptions
# ---- 配置 ----
BASE_URL = "https://accounts.x.ai"
SIGNUP_URL = "https://accounts.x.ai/sign-up?redirect=grok-com"
ACCOUNTS_FILE = "accounts_cli.txt"
CPA_DIR = "cpa_auths"
DOMAIN = "zhangyunuo.net"
CODE_TIMEOUT = 180
CODE_POLL = 3
IMAP_HOST = "imap.gmail.com"
IMAP_PORT = 993
EXTENSION_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "turnstilePatch")
CHROMIUM_SLIM_FLAGS = [
"--disable-gpu",
"--disable-software-rasterizer",
"--no-sandbox",
"--disable-dev-shm-usage",
"--disable-images",
"--mute-audio",
"--disable-background-networking",
"--no-first-run",
]
CODE_PATTERNS = [
re.compile(r"verification\s+code[:\s]+(\d{4,8})", re.I),
re.compile(r"your\s+code[:\s]+(\d{4,8})", re.I),
re.compile(r"confirm(?:ation)?\s+code[:\s]+(\d{4,8})", re.I),
re.compile(r"code[:\s]+(\d{4,8})", re.I),
re.compile(r"验证码[::\\s]+(\d{4,8})"),
]
def load_env(path=".env"):
data = {}
if not os.path.exists(path):
return data
with open(path) as f:
for line in f:
line = line.strip()
if not line or line.startswith("#"):
continue
if "=" in line:
k, v = line.split("=", 1)
data[k.strip()] = v.strip().strip('"').strip("'")
return data
def gen_alias(domain: str, length: int = 10) -> str:
alias = "".join(random.choices(string.ascii_lowercase + string.digits, k=length))
return f"{alias}@{domain}"
def gen_password(length: int = 16) -> str:
# Avoid form-hostile punctuation; keep strong enough for xAI signup rules.
lower = string.ascii_lowercase
upper = string.ascii_uppercase
digits = string.digits
special = "!@#$%^&*"
if length < 8:
length = 8
base = [
random.choice(lower),
random.choice(upper),
random.choice(digits),
random.choice(special),
]
pool = lower + upper + digits + special
base += random.choices(pool, k=length - len(base))
random.shuffle(base)
return "".join(base)
def decode_header(raw: str) -> str:
parts = _decode_header(raw or "")
out = []
for part, charset in parts:
if isinstance(part, bytes):
out.append(part.decode(charset or "utf-8", errors="replace"))
else:
out.append(part)
return "".join(out)
def message_body(msg) -> str:
body = ""
if msg.is_multipart():
for part in msg.walk():
ct = part.get_content_type()
if ct == "text/plain":
payload = part.get_payload(decode=True)
if payload:
body += payload.decode("utf-8", errors="replace")
else:
payload = msg.get_payload(decode=True)
if payload:
body += payload.decode("utf-8", errors="replace")
return body
def extract_code(text: str) -> str | None:
for pat in CODE_PATTERNS:
m = pat.search(text)
if m:
return m.group(1)
return None
def imap_login(user: str, password: str):
imap = imaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT, timeout=45)
imap.login(user, password)
imap.select("INBOX")
return imap
def fetch_code(imap, target_email: str, timeout: int = CODE_TIMEOUT, poll: int = CODE_POLL) -> str | None:
deadline = time.time() + timeout
target_lower = target_email.lower().strip()
while time.time() < deadline:
try:
status, data = imap.search(None, "ALL")
if status != "OK" or not data or not data[0]:
time.sleep(poll)
continue
msg_ids = data[0].split()[-30:]
filter_ts = int((time.time() - 900) * 1000)
for mid in reversed(msg_ids):
try:
_, msg_data = imap.fetch(mid, "(RFC822)")
except Exception:
continue
if not msg_data or not msg_data[0] or not isinstance(msg_data[0][1], bytes):
continue
msg = email_lib.message_from_bytes(msg_data[0][1])
date_str = msg.get("Date")
if date_str:
try:
dt = parsedate_to_datetime(date_str)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
if int(dt.timestamp() * 1000) < filter_ts:
continue
except Exception:
pass
subject = decode_header(msg.get("Subject", ""))
sender = decode_header(msg.get("From", ""))
recipient_blob = " ".join(
decode_header(msg.get(h, ""))
for h in ("To", "Cc", "Delivered-To", "X-Original-To", "Original-Recipient", "Envelope-To")
).lower()
if target_lower not in recipient_blob:
continue
body = message_body(msg)
combined = f"{subject}\n{sender}\n{recipient_blob}\n{body}".lower()
if not any(kw in combined for kw in ["x.ai", "xai", "grok", "verification", "code", "confirm", "验证码", "确认"]):
continue
code = extract_code(combined)
if code:
print(f"[+] Code found for {target_email}: {code}")
return code
except Exception as e:
print(f"[!] IMAP poll error: {e}")
time.sleep(poll)
return None
def save_account(email: str, password: str, sso: str = ""):
Path(ACCOUNTS_FILE).parent.mkdir(parents=True, exist_ok=True)
with open(ACCOUNTS_FILE, "a") as f:
f.write(f"{email}----{password}----{sso}\n")
print(f"[+] Saved: {email}")
def create_browser_options(headless: bool = False, proxy: str = "") -> ChromiumOptions:
opts = ChromiumOptions()
opts.auto_port()
for flag in CHROMIUM_SLIM_FLAGS:
opts.set_argument(flag)
if os.path.exists(EXTENSION_PATH):
opts.add_extension(EXTENSION_PATH)
if headless:
opts.headless()
if proxy:
try:
from urllib.parse import urlparse
u = urlparse(proxy if "://" in proxy else f"http://{proxy}")
host = u.hostname or ""
if host:
port = u.port or (443 if (u.scheme or "http") == "https" else 80)
scheme = u.scheme or "http"
opts.set_argument(f"--proxy-server={scheme}://{host}:{port}")
except Exception as e:
print(f"[!] Proxy setup failed: {e}")
return opts
def wait_for_tab(browser: Chromium, timeout: int = 15) -> any:
"""Wait for browser tab to be ready and return it."""
deadline = time.time() + timeout
while time.time() < deadline:
try:
tab_ids = browser.tab_ids
if tab_ids:
tab = browser.get_tab(tab_ids[0])
if tab is not None:
return tab
except Exception:
pass
time.sleep(0.5)
# Fallback
return browser.latest_tab
def bypass_turnstile(page) -> None:
"""Try to bypass Cloudflare Turnstile using multiple methods."""
try:
# Method 1: Check if Turnstile token already exists
token = page.run_js("""
try {
const byInput = String((document.querySelector('input[name="cf-turnstile-response"]') || {}).value || '').trim();
if (byInput) return byInput;
if (window.turnstile && typeof turnstile.getResponse === 'function') {
return String(turnstile.getResponse() || '').trim();
}
return '';
} catch(e) { return ''; }
""")
if token and len(token) >= 80:
print(f"[+] Turnstile token already present (len={len(token)})")
return
except Exception:
pass
try:
# Method 2: Try to find and click Turnstile checkbox
challenge_input = page.ele("@name=cf-turnstile-response", timeout=2)
if challenge_input:
try:
wrapper = challenge_input.parent()
iframe = None
try:
iframe = wrapper.shadow_root.ele("tag:iframe")
except Exception:
pass
if iframe:
try:
iframe.run_js("""
window.dtp = 1;
function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }
let sx = getRandomInt(800, 1200);
let sy = getRandomInt(400, 700);
Object.defineProperty(MouseEvent.prototype, 'screenX', { value: sx });
Object.defineProperty(MouseEvent.prototype, 'screenY', { value: sy });
""")
except Exception:
pass
try:
body_sr = iframe.ele("tag:body").shadow_root
btn = body_sr.ele("tag:input")
if btn:
btn.click()
print("[+] Clicked Turnstile checkbox via shadow DOM")
except Exception:
pass
except Exception:
pass
except Exception:
pass
try:
# Method 3: Click any visible Turnstile container
page.run_js("""
const nodes = Array.from(document.querySelectorAll('div,span,iframe')).filter((n) => {
const txt = (n.className || '') + ' ' + (n.id || '') + ' ' + (n.getAttribute?.('src') || '');
return String(txt).toLowerCase().includes('turnstile');
});
if (nodes.length && typeof nodes[0].click === 'function') nodes[0].click();
""")
except Exception:
pass
def click_email_signup_button(page, timeout: int = 15) -> bool:
"""Click the 'Sign up with email' button on the landing page."""
deadline = time.time() + timeout
while time.time() < deadline:
clicked = page.run_js("""
const candidates = Array.from(document.querySelectorAll('button, a, [role="button"]'));
const target = candidates.find((node) => {
const text = (node.innerText || node.textContent || '').replace(/\\s+/g, '');
const lower = text.toLowerCase();
return (
text.includes('使用邮箱注册') ||
lower.includes('signupwithemail') ||
lower.includes('continuewithemail') ||
lower.includes('email')
);
});
if (!target) return false;
target.click();
return true;
""")
if clicked:
print("[+] Clicked 'Sign up with email' button")
return True
time.sleep(1)
return False
def fill_email_js(page, email: str) -> bool:
"""Fill email input using React-compatible JS injection."""
deadline = time.time() + 15
while time.time() < deadline:
result = page.run_js("""
function isVisible(node) {
if (!node) return false;
const style = window.getComputedStyle(node);
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
const rect = node.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}
const input = Array.from(document.querySelectorAll('input[data-testid="email"], input[name="email"], input[type="email"], input[autocomplete="email"]')).find((node) => isVisible(node) && !node.disabled && !node.readOnly) || null;
if (!input) return 'not-ready';
input.focus(); input.click();
const valueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
const tracker = input._valueTracker;
if (tracker) tracker.setValue(arguments[0]);
if (valueSetter) valueSetter.call(input, arguments[0]); else input.value = arguments[0];
input.dispatchEvent(new Event('focus', { bubbles: true }));
input.dispatchEvent(new InputEvent('beforeinput', { bubbles: true, data: arguments[0], inputType: 'insertText' }));
input.dispatchEvent(new InputEvent('input', { bubbles: true, data: arguments[0], inputType: 'insertText' }));
input.dispatchEvent(new Event('change', { bubbles: true }));
input.dispatchEvent(new Event('blur', { bubbles: true }));
const current = (input.value || '').trim();
if (current === arguments[0]) return 'filled';
input.value = '';
input.dispatchEvent(new Event('input', { bubbles: true }));
for (const ch of arguments[0]) {
input.dispatchEvent(new KeyboardEvent('keydown', { key: ch, bubbles: true }));
input.value += ch;
input.dispatchEvent(new InputEvent('input', { bubbles: true, data: ch, inputType: 'insertText' }));
input.dispatchEvent(new KeyboardEvent('keyup', { key: ch, bubbles: true }));
}
input.dispatchEvent(new Event('change', { bubbles: true }));
if ((input.value || '').trim() === arguments[0]) return 'filled';
return input.value;
""", email)
if result == "not-ready":
time.sleep(0.5)
continue
if result == "filled":
return True
time.sleep(0.5)
return False
def click_continue_button(page) -> bool:
"""Click continue/signup button after email is filled."""
clicked = page.run_js(r"""
function isVisible(node) {
if (!node) return false;
const style = window.getComputedStyle(node);
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
const rect = node.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}
const input = Array.from(document.querySelectorAll('input[data-testid="email"], input[name="email"], input[type="email"], input[autocomplete="email"]')).find((node) => isVisible(node) && !node.disabled && !node.readOnly) || null;
if (!input || !(input.value || '').trim()) return false;
const buttons = Array.from(document.querySelectorAll('button[type="submit"], button')).filter((node) => isVisible(node) && !node.disabled && node.getAttribute('aria-disabled') !== 'true');
const submitButton = buttons.find((node) => {
const raw = (node.innerText || node.textContent || '').trim();
const compact = raw.replace(/\s+/g, '').toLowerCase();
return (
raw.includes('注册') ||
compact === 'signup' ||
compact.includes('signup') ||
compact.includes('continue') ||
compact.includes('next') ||
compact.includes('createaccount') ||
compact.includes('submit')
);
});
if (!submitButton || submitButton.disabled) return false;
submitButton.click();
return true;
""")
return bool(clicked)
def gen_name() -> tuple[str, str]:
first = ["Alex", "Sam", "Jordan", "Taylor", "Casey", "Riley", "Morgan", "Avery", "Quinn", "Jamie"]
last = ["Smith", "Johnson", "Brown", "Taylor", "Anderson", "Thomas", "Jackson", "White", "Harris", "Martin"]
return random.choice(first), random.choice(last)
def fill_profile_js(page, password: str, timeout: int = 120) -> dict:
"""Fill given/family name + password and submit after Turnstile is ready."""
given_name, family_name = gen_name()
deadline = time.time() + timeout
form_filled = False
while time.time() < deadline:
state = page.run_js(
r"""
const givenName = arguments[0];
const familyName = arguments[1];
const password = arguments[2];
function isVisible(node) {
if (!node) return false;
const style = window.getComputedStyle(node);
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
const rect = node.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}
function pick(sel) {
return Array.from(document.querySelectorAll(sel)).find((n) => isVisible(n) && !n.disabled && !n.readOnly) || null;
}
function setVal(input, value) {
if (!input) return false;
input.focus(); input.click();
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set;
const tracker = input._valueTracker;
if (tracker) tracker.setValue('');
if (setter) setter.call(input, value); else input.value = value;
input.dispatchEvent(new InputEvent('beforeinput', { bubbles: true, data: value, inputType: 'insertText' }));
input.dispatchEvent(new InputEvent('input', { bubbles: true, data: value, inputType: 'insertText' }));
input.dispatchEvent(new Event('change', { bubbles: true }));
input.blur();
return String(input.value || '').trim() === String(value || '').trim();
}
const givenInput = pick('input[data-testid="givenName"], input[name="givenName"], input[autocomplete="given-name"]');
const familyInput = pick('input[data-testid="familyName"], input[name="familyName"], input[autocomplete="family-name"]');
const passwordInput = pick('input[data-testid="password"], input[name="password"], input[type="password"], input[autocomplete="new-password"]');
if (!givenInput || !familyInput || !passwordInput) return 'not-ready';
const ok1 = setVal(givenInput, givenName);
const ok2 = setVal(familyInput, familyName);
const ok3 = setVal(passwordInput, password);
if (!ok1 || !ok2 || !ok3) return 'fill-failed';
const cfInput = document.querySelector('input[name="cf-turnstile-response"]');
const cfPresent = !!cfInput || !!document.querySelector('iframe[src*="turnstile"], div.cf-turnstile, [data-sitekey]');
if (cfPresent) {
const token = String((cfInput && cfInput.value) || '').trim();
if (token.length < 80) return 'wait-cf:' + token.length;
}
const buttons = Array.from(document.querySelectorAll('button[type="submit"], button')).filter((n) => isVisible(n) && !n.disabled && n.getAttribute('aria-disabled') !== 'true');
const submitBtn = buttons.find((n) => {
const t = (n.innerText || n.textContent || '').replace(/\s+/g, '').toLowerCase();
return t.includes('完成注册') || t.includes('创建账户') || t.includes('signup') || t.includes('signup') || t.includes('createaccount') || t.includes('continue') || t.includes('confirm');
});
if (!submitBtn) return 'filled-no-submit';
submitBtn.click();
return 'submitted';
""",
given_name,
family_name,
password,
)
if state == "not-ready":
time.sleep(0.8)
continue
if state == "fill-failed":
time.sleep(0.5)
continue
if isinstance(state, str) and state.startswith("wait-cf"):
form_filled = True
bypass_turnstile(page)
time.sleep(1.5)
continue
if state == "filled-no-submit":
form_filled = True
time.sleep(1)
continue
if state == "submitted":
return {"ok": True, "given_name": given_name, "family_name": family_name, "password": password}
time.sleep(0.8)
return {"ok": False, "error": "timeout waiting profile/turnstile", "given_name": given_name, "family_name": family_name}
def wait_for_sso(page, timeout: int = 180) -> str:
deadline = time.time() + timeout
last_click = 0.0
while time.time() < deadline:
# periodically re-click final submit if still on complete-registration page
now = time.time()
if now - last_click >= 3:
try:
page.run_js(r"""
function isVisible(node) {
if (!node) return false;
const style = window.getComputedStyle(node);
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
const rect = node.getBoundingClientRect();
return rect.width > 0 && rect.height > 0;
}
const titleHit = !!Array.from(document.querySelectorAll('h1,h2,div,span')).find((el) => {
const t = (el.textContent || '').replace(/\s+/g, '');
return t.includes('完成注册') || t.toLowerCase().includes('complete');
});
if (!titleHit) return false;
const cfInput = document.querySelector('input[name="cf-turnstile-response"]');
if (cfInput && String(cfInput.value || '').trim().length < 80) return false;
const buttons = Array.from(document.querySelectorAll('button[type="submit"], button')).filter((n) => isVisible(n) && !n.disabled);
const submitBtn = buttons.find((n) => {
const t = (n.innerText || n.textContent || '').replace(/\s+/g, '').toLowerCase();
return t.includes('完成注册') || t.includes('创建账户') || t.includes('signup') || t.includes('signup') || t.includes('createaccount');
});
if (submitBtn) { submitBtn.click(); return true; }
return false;
""")
except Exception:
pass
last_click = now
try:
cookies = None
for getter in (
lambda: page.cookies(all_domains=True, all_info=True),
lambda: page.cookies(all_domains=True),
lambda: page.cookies(),
):
try:
cookies = getter()
if cookies:
break
except TypeError:
continue
except Exception:
continue
if isinstance(cookies, list):
for c in cookies:
if not isinstance(c, dict):
continue
if str(c.get("name", "")).strip() == "sso" and c.get("value"):
return str(c.get("value")).strip()
except Exception:
pass
time.sleep(1)
return ""
def maybe_export_cpa(email: str, password: str, sso: str, page) -> str | None:
"""Best-effort CPA export using project cpa_export + config.json."""
cfg_path = Path(__file__).resolve().parent / "config.json"
if not cfg_path.is_file():
return None
try:
cfg = json.loads(cfg_path.read_text(encoding="utf-8"))
except Exception:
return None
if not cfg.get("cpa_export_enabled", True):
return None
try:
import cpa_export
cookies = cpa_export.export_cookies_from_page(page)
result = cpa_export.export_cpa_xai_for_account(
email,
password,
page=None,
cookies=cookies,
sso=sso,
config=cfg,
log_callback=lambda m: print(f"[cpa] {m}"),
)
if result.get("ok") and result.get("path"):
return str(result["path"])
except Exception as exc:
print(f"[!] CPA export exception: {exc}")
return None
def do_register(alias: str, password: str, imap_user: str, imap_password: str, headless: bool = False, proxy: str = "") -> dict:
email = alias
print(f"[*] Registering: {email}")
opts = create_browser_options(headless=headless, proxy=proxy)
browser = None
imap_conn = None
try:
browser = Chromium(opts)
page = wait_for_tab(browser, timeout=20)
if page is None:
raise Exception("无法获取浏览器标签页")
page.get(SIGNUP_URL)
page.wait.doc_loaded(timeout=20)
time.sleep(2)
print(f"[+] Page loaded: {page.url}")
# 尝试绕过 Turnstile
bypass_turnstile(page)
time.sleep(1)
# 点击"使用邮箱注册"按钮
if not click_email_signup_button(page, timeout=10):
print("[!] No email signup button found, continuing anyway...")
time.sleep(2)
# 填写邮箱
if not fill_email_js(page, email):
raise Exception("未找到邮箱输入框或写入失败")
print(f"[+] Email submitted: {email}")
time.sleep(0.8)
# 点击继续
if not click_continue_button(page):
raise Exception("未找到注册按钮")
print(f"[+] Continue clicked")
time.sleep(3)
# 连接 IMAP 准备取验证码
print(f"[*] Connecting IMAP {imap_user}...")
imap_conn = imap_login(imap_user, imap_password)
# 等待验证码
print(f"[*] Waiting for code...")
code = fetch_code(imap_conn, email, timeout=CODE_TIMEOUT, poll=CODE_POLL)
if not code:
raise Exception(f"未收到验证码: {email}")
# 填写验证码
code_input = page.ele(
"css:input[data-input-otp='true'], input[name='code'], input[autocomplete='one-time-code']",
timeout=5,
)
if code_input:
code_input.clear()
code_input.input(code)
else:
boxes = page.eles("css:input[inputmode='numeric']")
for i, ch in enumerate(code):
if i < len(boxes):
boxes[i].input(ch)
time.sleep(0.1)
print(f"[+] Code filled: {code}")
time.sleep(0.5)
# 点击确认
page.run_js("""
const btns = Array.from(document.querySelectorAll('button'));
const btn = btns.find(b => {
const t = (b.innerText || b.textContent || '').replace(/\\s+/g, '').toLowerCase();
return t.includes('continue') || t.includes('confirm') || t.includes('下一步');
});
if (btn) btn.click();
""")
time.sleep(3)
# 验证码后通常进入资料页:姓名 + 密码 + Turnstile
print("[*] Filling profile (name/password)...")
profile = fill_profile_js(page, password, timeout=120)
if not profile.get("ok"):
raise Exception(f"资料填写失败: {profile.get('error') or 'unknown'}")
print(f"[+] Profile filled: {profile.get('given_name')} {profile.get('family_name')}")
# 等待 sso cookie(最终提交后)
print("[*] Waiting for sso cookie...")
sso_cookie = wait_for_sso(page, timeout=180)
current_url = page.url
print(f"[+] Current URL: {current_url}")
if not sso_cookie:
raise Exception("未获取到 sso cookie")
save_account(email, password, sso_cookie)
result = {"ok": True, "email": email, "password": password, "sso": sso_cookie, "url": current_url}
# 可选:协议优先 CPA mint
if os.environ.get("CPA_EXPORT", "1") not in ("0", "false", "False"):
try:
cpa_path = maybe_export_cpa(email, password, sso_cookie, page)
if cpa_path:
result["cpa_path"] = cpa_path
print(f"[+] CPA auth: {cpa_path}")
except Exception as cpa_exc:
print(f"[!] CPA export failed: {cpa_exc}")
result["cpa_error"] = str(cpa_exc)
return result
except Exception as e:
print(f"[-] Failed: {e}")
try:
if browser is not None:
tab = wait_for_tab(browser, timeout=5)
if tab is not None:
tab.get_screenshot(path="register_error.png", full_page=False)
print(f"[!] Screenshot: register_error.png")
except Exception:
pass
return {"ok": False, "error": str(e), "email": email}
finally:
if imap_conn:
try:
imap_conn.close()
except Exception:
pass
try:
imap_conn.logout()
except Exception:
pass
if browser is not None:
try:
browser.quit()
except Exception:
pass
def main():
parser = argparse.ArgumentParser(description="Grok 自动注册(Cloudflare catch-all + Gmail IMAP)")
parser.add_argument("--count", type=int, default=1)
parser.add_argument("--threads", type=int, default=1)
parser.add_argument("--headless", action="store_true")
parser.add_argument("--proxy", default=None)
parser.add_argument("--domain", default=DOMAIN)
parser.add_argument("--imap-user", default=None)
parser.add_argument("--imap-password", default=None)
args = parser.parse_args()
env = load_env()
proxy = args.proxy or env.get("proxy") or env.get("cpa_proxy") or ""
if proxy == "http://127.0.0.1:7890":
proxy = ""
imap_user = args.imap_user or env.get("GMAIL_USER") or ""
imap_password = args.imap_password or env.get("GMAIL_PASSWORD") or ""
if not imap_user or not imap_password:
print("Error: IMAP credentials required via --imap-user/--imap-password or GMAIL_USER/GMAIL_PASSWORD")
return 1
print(f"[*] Domain: {args.domain}")
print(f"[*] Proxy: {proxy or 'direct'}")
print(f"[*] IMAP: {imap_user}")
print(f"[*] Count: {args.count}, Threads: {args.threads}")
tasks = []
for i in range(args.count):
tasks.append((i + 1, gen_alias(args.domain), gen_password()))
results = []
threads = max(1, min(int(args.threads or 1), 10))
if threads == 1:
for idx, alias, password in tasks:
print(f"\n=== Account {idx}/{args.count} ===")
res = do_register(alias, password, imap_user, imap_password, headless=args.headless, proxy=proxy or None)
results.append(res)
if idx < args.count:
time.sleep(3)
else:
from concurrent.futures import ThreadPoolExecutor, as_completed
print(f"[*] Running with {threads} threads")
with ThreadPoolExecutor(max_workers=threads) as ex:
futs = {
ex.submit(
do_register, alias, password, imap_user, imap_password, args.headless, proxy or None
): (idx, alias)
for idx, alias, password in tasks
}
for fut in as_completed(futs):
idx, alias = futs[fut]
try:
res = fut.result()
except Exception as exc:
res = {"ok": False, "email": alias, "error": str(exc)}
results.append(res)
status = "OK" if res.get("ok") else "FAIL"
print(f"[{status}] #{idx} {res.get('email')} {res.get('error', '')[:80]}")
print(f"\n=== Summary ===")
ok = sum(1 for r in results if r.get("ok"))
print(f"Success: {ok}/{len(results)}")
for r in results:
status = "OK" if r.get("ok") else "FAIL"
extra = r.get("cpa_path") or r.get("error", "")
print(f" [{status}] {r.get('email')} - {str(extra)[:100]}")
return 0 if ok else 1
if __name__ == "__main__":
raise SystemExit(main() or 0)