186 lines
6.6 KiB
Python
186 lines
6.6 KiB
Python
#!/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() |