232 lines
10 KiB
Python
232 lines
10 KiB
Python
# -*- 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)
|