#!/usr/bin/env python3 """ Parallel.ai Auto-Registration Script Uses t-online.de emails (IMAP) → magic link → create org → API key Usage: python parallel_autoreg.py --count 5 python parallel_autoreg.py --emails emails.txt --count 10 --output keys.json """ import imaplib, ssl, time, re, json, sys, os import requests from urllib.parse import unquote from datetime import datetime, timezone from email.utils import parsedate_to_datetime import email as eml # Config IMAP_HOST = 'imap.t-online.de' IMAP_PORT = 993 PARALLEL_BASE = 'https://platform.parallel.ai' OUTPUT_FILE = 'parallel_keys.json' EMAILS_FILE = 'emails.txt' # Override with --emails def create_ssl_context(): ctx = ssl.create_default_context() ctx.check_hostname = False ctx.verify_mode = ssl.CERT_NONE return ctx def load_emails(filepath, limit=None): """Load unique email:password pairs from file, deduplicating by email""" seen = set() emails = [] with open(filepath, 'r', encoding='utf-8', errors='replace') as f: for line in f: line = line.strip() if not line or line.startswith('#'): continue line = line.replace(';', ':') parts = line.split(':') if len(parts) >= 2: email_addr = parts[0].strip().strip('"').strip("'") password = [REDACTED]].strip().strip('"').strip("'") if email_addr not in seen and '@t-online.de' in email_addr: seen.add(email_addr) emails.append((email_addr, password)) if limit and len(emails) >= limit: break return emails def send_magic_link(session, email_addr): """Send magic link via Parallel API""" csrf = session.get(f"{PARALLEL_BASE}/api/auth/csrf").json()['csrfToken'] r = session.post(f"{PARALLEL_BASE}/api/auth/signin/email", data={ "email": email_addr, "csrfToken": csrf, "callbackUrl": f"{PARALLEL_BASE}/post-auth-sign-up" }, allow_redirects=False) return r.status_code == 302 def poll_imap_for_link(email_addr, password, sent_time, max_attempts=12): """Poll IMAP inbox for magic link, filtering by email date""" ctx = create_ssl_context() mail = imaplib.IMAP4_SSL(IMAP_HOST, IMAP_PORT, ssl_context=ctx, timeout=10) mail.login(email_addr, password) for attempt in range(max_attempts): time.sleep(2.5) mail.noop() mail.select('INBOX') status, messages = mail.search(None, 'ALL') msg_ids = messages[0].split() if messages[0] else [] for msg_id in reversed(msg_ids[-5:]): status, data = mail.fetch(msg_id, '(RFC822)') raw = data[0][1] msg = eml.message_from_bytes(raw) date_str = msg.get('Date', '') try: msg_date = parsedate_to_datetime(date_str) except: msg_date = None if b'Parallel' in raw and msg_date and msg_date > sent_time: for part in msg.walk(): if part.get_content_type() == 'text/plain': body = part.get_payload(decode=True).decode('utf-8', errors='replace') links = re.findall(r'https://platform\.parallel\.ai/redirect\?to=[^\s"<>]+', body) if links: to_match = re.search(r'to=([^&]+)', links[0]) callback_url = unquote(to_match.group(1)) mail.logout() return callback_url mail.logout() return None def create_org_and_get_key(session, org_name="Research"): """Create org and get API key""" r = session.post(f"{PARALLEL_BASE}/api/rpc/onboard/create-org-with-visibility", json={"orgName": org_name, "visibility": "private"}) if r.status_code != 200: return None r = session.post(f"{PARALLEL_BASE}/api/rpc/auth/get-my-api-key-and-session-details", json={}) if r.status_code != 200: return None data = r.json() api_key = [REDACTED]'data', {}).get('apiKey', '') org_id = data.get('data', {}).get('forOrgId', '') return { 'api_key': api_key, 'org_id': org_id, 'org_name': org_name, 'base_url': data.get('data', {}).get('baseUrl', 'https://api.parallel.ai/'), } def register_one(email_addr, password, org_name="Research"): """Register one account""" session = requests.Session() if not send_magic_link(session, email_addr): return {'error': 'Failed to send magic link'} sent_time = datetime.now(timezone.utc) callback_url = poll_imap_for_link(email_addr, password, sent_time) if not callback_url: return {'error': 'No magic link received'} session.get(callback_url, allow_redirects=True) s = session.get(f"{PARALLEL_BASE}/api/auth/session").json() if not s or 'user' not in s: return {'error': 'Session not established'} result = create_org_and_get_key(session, org_name) if not result: return {'error': 'Failed to create org/get key'} result['email'] = email_addr result['session_user'] = s['user']['email'] return result def main(): import argparse parser = argparse.ArgumentParser(description='Parallel.ai Auto-Registration') parser.add_argument('--count', type=int, default=1) parser.add_argument('--emails', type=str, default=EMAILS_FILE) parser.add_argument('--output', type=str, default=OUTPUT_FILE) parser.add_argument('--org-name', type=str, default='Research') args = parser.parse_args() emails = load_emails(args.emails, limit=args.count) print(f"Loaded {len(emails)} unique emails from {args.emails}") results = [] for i, (email_addr, password) in enumerate(emails): print(f"\n[{i+1}/{len(emails)}] {email_addr}") try: result = register_one(email_addr, password, args.org_name) if 'api_key' in result: print(f" ✅ API Key: {result['api_key'][:30]}...") results.append(result) else: print(f" ❌ {result.get('error', 'Unknown error')}") except Exception as e: print(f" ❌ Exception: {e}") with open(args.output, 'w') as f: json.dump(results, f, indent=2) if i < len(emails) - 1: time.sleep(3) print(f"\n=== Done: {len(results)}/{len(emails)} keys → {args.output} ===") for r in results: print(f" {r['email']}: {r['api_key'][:40]}...") if __name__ == '__main__': main()