Files

186 lines
6.6 KiB
Python
Executable File

#!/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()