Cortesía de opencode XD.

#!/bin/bash
# Descargar historias de Instagram desde iqsaved.com usando chromium headless

USERNAME="${1:-}"
OUTPUT_DIR="${2:-./instagram_stories}"

if [ -z "$USERNAME" ]; then
    echo "Uso: $0 <nombre_usuario> [directorio_salida]"
    echo "Ejemplo: $0 male_leoncini /tmp/mis_historias"
    exit 1
fi

URL="https://iqsaved.com/viewer/$USERNAME"
echo "Descargando historias de: $URL"

mkdir -p "$OUTPUT_DIR"
cd "$OUTPUT_DIR"

# Iniciar chromium con modo headless y debug remoto
echo "Iniciando chromium..."
chromium --headless --disable-gpu --no-sandbox --remote-debugging-port=9222 "$URL" > /dev/null 2>&1 &
CHROME_PID=$!

# Esperar a que chrome este listo
sleep 4

# Obtener WebSocket URL de la pagina
WS_URL=$(curl -s http://localhost:9222/json | python3 -c "
import sys, json
pages = json.load(sys.stdin)
target = [p for p in pages if '$USERNAME' in p.get('url','') or 'iqsaved' in p.get('url','')]
if target:
    print(target[0]['webSocketDebuggerUrl'])
" 2>/dev/null)

if [ -z "$WS_URL" ]; then
    echo "Error: No se pudo obtener WebSocket URL"
    kill $CHROME_PID 2>/dev/null
    exit 1
fi

echo "WebSocket: $WS_URL"

# Instalar ws si no existe
WS_DIR="/tmp/ws_cdp"
if [ ! -d "$WS_DIR" ]; then
    mkdir -p "$WS_DIR"
    cd "$WS_DIR"
    npm init -y > /dev/null 2>&1
    npm install ws > /dev/null 2>&1
fi

# Crear script para extraer URLs
cat > "$WS_DIR/get_stories.js" << 'SCRIPT_EOF'
const WebSocket = require('ws');

const ws = new WebSocket(process.argv[2]);
let cmdId = 0;

const send = (method, params={}) => {
  return new Promise((resolve) => {
    const id = ++cmdId;
    ws.send(JSON.stringify({id, method, params}));
    ws.once('message', (msg) => {
      const data = JSON.parse(msg);
      resolve(data.result || data);
    });
  });
};

ws.on('open', async () => {
  console.error('Esperando que carguen las historias (12s)...');
  await new Promise(r => setTimeout(r, 12000));
  
  const r = await send('Runtime.evaluate', {
    expression: `JSON.stringify({
      downloads: [...document.querySelectorAll('a[download],a[href$=".mp4"],a[href$=".jpg"]')]
        .map(a => ({href: a.href, text: a.getAttribute('download') || a.text.trim()}))
        .filter(a => a.href.includes('cdn.iqsaved'))
    })`
  });
  
  // El resultado viene anidado: r.result.value es un string JSON
  const value = r.result ? r.result.value : r.value;
  process.stdout.write(value);
  ws.close();
});
ws.on('error', e => { console.error('Error:', e.message); process.exit(1); });
SCRIPT_EOF

# Ejecutar y extraer URLs (solo stdout, ignorar stderr)
JSON_RESULT=$(cd "$WS_DIR" && node get_stories.js "$WS_URL" 2>/dev/null)

# Cerrar chromium
kill $CHROME_PID 2>/dev/null

# Parsear y descargar
echo "$JSON_RESULT" | python3 -c "
import sys, json, subprocess, re, os
from urllib.parse import unquote

try:
    data = json.load(sys.stdin)
    downloads = data.get('downloads', [])
    
    if not downloads:
        print('No se encontraron descargas')
        sys.exit(0)
    
    print(f'Encontrados {len(downloads)} archivos')
    
    for i, item in enumerate(downloads, 1):
        url = item['href']
        # Extraer nombre de archivo o generar uno
        if 'filename=' in url:
            fname = url.split('filename=')[1].split('&')[0]
            fname = unquote(fname)
        else:
            fname = f'file_{i}'
        
        # Determinar extension
        if '.mp4' in fname:
            ext = 'mp4'
        elif '.jpg' in fname or '.jpeg' in fname:
            ext = 'jpg'
        else:
            ext = 'bin'
        
        outfile = f'{i}.{ext}'
        
        # Skip if already exists and has content
        if os.path.exists(outfile) and os.path.getsize(outfile) > 1000:
            print(f'Omitiendo {outfile} (ya existe)')
            continue
        
        print(f'Descargando {outfile}...')
        
        subprocess.run(['wget', '-q', '-O', outfile, url], check=True)
    
    print('Descarga completa!')
except Exception as e:
    print(f'Error: {e}')
    sys.exit(1)
"

echo "Archivos en: $(pwd)"
ls -la