Este proyecto sirve animaciones ASCII por HTTP, pensadas para clientes de terminal como curl. El servidor principal es index.js (Node). Las animaciones se leen desde carpetas con archivos de texto (frames), por ejemplo frames/, pedro/, sexy/, etc.
El servidor envía las líneas de cada frame en streaming, manteniendo la conexión abierta para simular una animación en la terminal.
- Archivo principal:
index.js— servidor HTTP que transmite frames. - Carpeta por defecto:
frames/— contiene los cuadros de la animación. - Comandos:
npm start(producción),npm run dev(nodemon para desarrollo).
- Node.js (v14+ recomendada)
- npm
- Instalar dependencias:
cd /ruta/al/proyecto
npm install- Iniciar el servidor:
- Modo producción:
npm start- Modo desarrollo (auto-reload):
npm run devEl servidor por defecto escucha en el puerto 3000 o en la variable de entorno PARROT_PORT.
- Abrir la animación por defecto:
curl "http://localhost:3000"- Voltear los frames:
curl "http://localhost:3000?flip=true"- Elegir otra carpeta de animación (por ejemplo
pedro):
curl "http://localhost:3000?folder=pedro"- Cambiar la velocidad de la animación (delay en ms):
curl "http://localhost:3000?delay=40"- Enviar la velocidad mediante header HTTP (útil con
curl -H):
curl -H "X-Parrot-Delay: 120" "http://localhost:3000?folder=frames"folder— nombre de carpeta con frames (ej.:frames,pedro). Sólo se aceptan nombres seguros:[A-Za-z0-9_-].flip—true|false— invierte (voltea) cada frame.delay— número en milisegundos entre frames (por defecto 80ms).color—true|false|0|1|no|off— cuando esfalse, la salida no usa colores.
Prioridad para el delay:
- Query param
delay(ej.?delay=40) - Header
X-Parrot-DelayoX-Delay - Flag CLI
--delayo variable de entornoPARROT_DELAY - Valor por defecto en el código (80ms)
- Crear una carpeta en la raíz, por ejemplo
myparrot/. - Añadir archivos por orden (
0.txt,1.txt,2.txt, ...). Cada archivo contiene el ASCII-art del frame. - Consumir la animación:
curl "http://localhost:3000?folder=myparrot"El servidor validará que la carpeta existe y que el nombre es seguro.
La aplicación mantiene conexiones abiertas (streaming), por lo que plataformas que no soporten conexiones largas (ciertas serverless) no son ideales. Recomendaciones:
- Fly.io — funciona muy bien para mantener conexiones abiertas.
- Render o Railway — opciones sencillas.
- VPS / Docker — control completo.
Ejemplo de Dockerfile mínimo:
FROM node:20-alpine
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
ENV PORT 3000
EXPOSE 3000
CMD ["node", "index.js"]folderse valida para evitar directory traversal.- Cada conexión de cliente mantiene recursos en el servidor; planifica la concurrencia según tu despliegue.
loops(int): detener la animación después de N ciclos.- Soporte para archivos
.animationque contengan múltiples frames en un solo fichero. - Control de
rateLimit/maxClients(requiere almacenamiento en memoria o Redis para producción).
Si quieres que implemente alguna de estas mejoras, dímelo y lo hago.
Esta sección describe las partes principales del código en index.js, el flujo de ejecución y las decisiones de diseño para que puedas extender o mantener el proyecto más fácilmente.
-
Preload y cache de frames
framesCache(Map): cachea por nombre de carpeta un objeto { original: [frames], flipped: [frames] }.- Al inicio el servidor intenta precargar la carpeta
frames/llamando aloadFrames('frames')dentro de un IIFE asíncrono.
-
Carga de frames:
loadFrames(folder)- Usa
mz/fspara llamadas basadas en Promises (readdir,readFile). - Lee todos los archivos de la carpeta (según
fs.readdir) y hacePromise.alldefs.readFilepara cada archivo. - Convierte cada buffer a string y construye
original(array de strings) yflipped(cada string invertido consplit('').reverse().join('')). - Nota:
fs.readdirno garantiza orden numérico; si tus frames están nombrados0.txt, 1.txt, ...y quieres orden seguro, añade unfiles.sort()con comparación numérica antes de mapear.
- Usa
-
Selección de color
colorsOptionscontiene la paleta usada (por defecto: red, yellow, green, blue, magenta, cyan, white).selectColor(previousColor)elige un índice aleatorio distinto apreviousColorpara evitar repetir el mismo color dos frames seguidos.- El coloreado se aplica usando la librería
colorscuandouseColorsestá activo.
-
Parsing de flags/entorno
parseCliArgs()maneja--delay 120o--delay=120(valores pasados anode index.js).DEFAULT_DELAYse determina en orden: CLI > variablePARROT_DELAY> undefined (después el código usa 80ms por defecto si no hay nada).
-
Validación de la petición:
validateQuery(query)- Extrae
flipcomo boolean ('true'),foldersólo si cumple la regex/^[a-zA-Z0-9_\-]+$/(evita../),delaycomo número,colora través deshouldUseColors(). - Esto mitiga directory traversal y entradas malformadas.
- Extrae
-
Manejo de la petición HTTP (flujo principal)
- El servidor rechaza peticiones que no parecen venir de
curl(siUser-Agentexiste y NO incluyecurlhace un 302 redirigiendo al repo). Esto mantiene el foco en clientes de terminal. - Crea una
Readablestream (con_read = noop) ystream.pipe(res)para que lospush()envíen datos al cliente conectado. - Analiza query params con
validateQuery(url.parse(req.url, true).query). - Permite override de
delayvía headersX-Parrot-DelayoX-Delaysi el query param dedelayno está presente. - Determina
folderName(por defectoframes) y busca enframesCache. Si no existe, valida que la carpeta existe (fs.stat) y llamaloadFrames(folderName)para leer y cachear. - Llama a
streamer(stream, { flip, frames, delay, color })que devuelve elintervaldesetInterval. - Registra
req.on('close', ...)parastream.destroy()yclearInterval(interval)cuando el cliente cierra la conexión: limpieza necesaria para evitar leaks de memoria/timers.
- El servidor rechaza peticiones que no parecen venir de
-
Implementación del
streamer(stream, opts)- Toma
opts.frames(original y flipped),opts.flippara decidir cuál usar,opts.delayyopts.color. - Calcula
delaycon prioridad:opts.delay->DEFAULT_DELAY-> 80ms. - Crea un
setIntervalque cada tick:- Inserta ANSI codes para limpiar pantalla:
\x1b[2J\x1b[3J\x1b[H. - Si
useColors, selecciona color distinto al anterior y hacestream.push(colors[color](frames[index]));. - Si no, hace
stream.push(frames[index]);. - Incrementa
indexmodulando porframes.length.
- Inserta ANSI codes para limpiar pantalla:
- Devuelve el ID del interval para que el caller pueda
clearInterval.
- Toma
-
Manejo de errores
- La lectura de frames y la lógica asíncrona está envuelta en try/catch (promesas), y al detectar problemas envía
500o404según corresponda. - Si ocurre un error después de que ya se enviaron headers, se intenta
res.end(...)dentro de untrypara evitar excepciones adicionales.
- La lectura de frames y la lógica asíncrona está envuelta en try/catch (promesas), y al detectar problemas envía
-
Dependencias clave
mz/fs— promisified fs helpers (readdir, readFile, stat).colors— formatea texto con colores ANSI.http,url,path,stream(nativos de Node).
-
Puntos de extensión y mejoras (técnicas)
- Ordenar archivos de
fs.readdir()numéricamente antes de construir frames para evitar problemas de orden. - Añadir soporte para
loops: hacer questreamercuente vueltas y seclearIntervalcuando alcanza N. - Implementar
palette/paletteparam para personalizarcolorsOptionspor petición o por carpeta. - Servir frames desde archivos
.animation(parsear delimitadores) o desde un JSON preprocesado. - Añadir tests unitarios para
validateQuery,shouldUseColors,loadFrames(mockfs), y tests de integración que arranquen el servidor y usencurlosupertestpara verificar streaming y limpieza.
- Ordenar archivos de
-
Consideraciones y edge-cases
- Concurrencia: cada cliente produce un timer y mantiene la memoria de los frames (compartida via cache). Si hay muchos clientes simultáneos, vigila el uso de timers y la memoria.
- Orden de frames:
fs.readdir()puede devolver orden inesperado; ordenar por nombre o parsear índices evita cuadros fuera de secuencia. - Robustez del
flip: el flipping actual invierte cada string; para algunos ASCII-art esto está bien, para otros quizá quieras transformaciones más sofisticadas. - Validación de contenido: podrías comprobar tamaños de frame/longitud para evitar que frames extremadamente grandes saturen el buffer.
Documento actualizado para incluir la explicación interna del servidor y puntos para extenderlo o probarlo.
Documento generado y actualizado en español para explicar el funcionamiento básico, parámetros de uso, ejemplos y despliegue.