✅ Instagram - Posts, Reels, Stories y Videos Privados (cookies + proxy de embeds)
✅ LinkedIn - Videos de posts y Galerías de imágenes (descarga secuencial)
✅ X (Twitter) - Videos de tweets (alta calidad garantizada)
✅ TikTok - Videos sin marca de agua
✅ Facebook - Videos públicos, Reels y enlaces share/v/ (múltiples fallbacks)
✅ YouTube - Videos y Shorts (Calidad Optimizada hasta 1080p + Audio)
✅ Pinterest - Videos e Imágenes (Pin original)
✅ Twitch - Clips y VODs
✅ Threads - Videos de posts (vía navegador headless en el backend residencial)
Es un solo código (este repo). GitHub lo guarda; corre en dos lugares a la vez:
Usuarios (celular / web)
│
▼
Railway (nube, URL pública fija) ── contenido público → lo baja directo
│
│ contenido con login (Instagram/Facebook) o Threads
▼
Tu PC (backend residencial, vía Cloudflare Tunnel)
IP de casa + cookie de Meta + navegador (Playwright)
Por qué: Meta (Instagram/Facebook/Threads) bloquea las IPs de datacenter como la de Railway (HTTP 400 aunque la cookie sea válida). El pedido tiene que salir de una IP residencial. Entonces:
- Railway es la única URL pública, siempre online. Sirve todo el contenido público (YouTube, TikTok, X, Pinterest, Twitch, LinkedIn, reels públicos).
- Cuando un contenido exige login o es de Threads, Railway reenvía el pedido a tu PC (registrada vía Cloudflare Tunnel). Tu PC lo resuelve con IP residencial + cookie y devuelve el resultado. Transparente para el usuario.
- Si tu PC está apagada: lo público sigue andando; lo de login/Threads no, hasta que la prendas.
Componentes:
src/server.py— endpoint/api/admin/set-fallback(tu PC registra su URL, protegido porADMIN_SECRET) y reenvío automático enhandle_extractcuando el resultado traeneeds_remoteo es login-gated.home_tunnel.py— corre en tu PC: abre el túnel Cloudflare, captura la URL pública y la registra en Railway (re-registra cada 2 min y cuando la URL cambia).tunnel.bat— un clic: levanta la app local +home_tunnel.py.- Playwright NO está en
requirements.txt(Railway no lo instala); tu venv local sí lo tiene → por eso Threads corre solo en tu PC.
- UI/UX Unificada: Misma experiencia para todas las plataformas desde una sola interfaz.
- Calidad Optimizada:
- YouTube: Selección inteligente de video (1080p/720p) + audio fusionado.
- X/Twitter: Enrutamiento especial para evitar errores 403.
- Instagram: Soporte para historias y cuentas privadas (usando cookies del navegador).
- Descargas Inteligentes:
- LinkedIn: Detecta si es video o galería. Si es galería, descarga imágenes secuencialmente (img-1, img-2...).
- Archivos ZIP: Empaquetado automático para descargas múltiples.
- Nombres de Archivo Unificados: Todas las descargas siguen el formato
plataforma_titulo_fecha.ext(ej.instagram_Mar_del_Plata_Drone_20260702.mp4). Generado porbuildFilename()ensrc/common/card.js. - Autenticación con Cookies (Instagram): Soporte para archivo de cookies Netscape en
cookies/instagram.txt(o variable de entornoINSTAGRAM_COOKIES_FILE). Necesario porque Instagram exige login para la mayoría de los reels. Exportar con la extensión "Get cookies.txt LOCALLY". El directoriocookies/está en.gitignore. - Interfaz Responsiva: Diseño moderno, adaptable a móviles y escritorio.
- Logging Detallado: Información completa de resolución, bitrate y errores en consola.
- API REST Local: Endpoints para validación y extracción, listos para integración.
c:\www\video-redes\
├── src/
│ ├── server.py # Servidor principal (Entry Point)
│ ├── common/ # Recursos compartidos
│ │ ├── card.js # Tarjeta de video + buildFilename()
│ │ ├── ytdlp_cmd.py # Resolución del ejecutable yt-dlp (venv/PATH/módulo)
│ │ ├── cookies_util.py # Combina todos los .txt de cookies/ en uno
│ │ └── style.css # Estilos globales
│ ├── youtube/ # Módulo YouTube
│ │ ├── youtube_extractor.py # Extracción de videos/shorts
│ │ └── youtube.js # Lógica de presentación
│ ├── instagram/ # Módulo Instagram
│ │ ├── insta_extractor.py # Extracción con soporte cookies
│ │ └── insta.js # Lógica de presentación
│ ├── tiktok/ # Módulo TikTok
│ │ ├── tiktok_extractor.py # Extracción con yt-dlp
│ │ └── tiktok.js # Lógica de presentación
│ ├── facebook/ # Módulo Facebook
│ │ ├── facebook_extractor.py # Extracción con fallbacks
│ │ └── facebook.js # Lógica de presentación
│ ├── linkedin/ # Módulo LinkedIn
│ │ ├── linkedin_extractor.py # Videos + galerías + PDFs
│ │ └── linkedin.js # Lógica de presentación
│ ├── x/ # Módulo X (Twitter)
│ │ ├── x_extractor.py # Extracción de tweets
│ │ └── x.js # Lógica de presentación
│ ├── pinterest/ # Módulo Pinterest
│ │ ├── pinterest_extractor.py
│ │ └── pinterest.js
│ ├── twitch/ # Módulo Twitch
│ │ ├── twitch_extractor.py
│ │ └── twitch.js
│ └── threads/ # Módulo Threads (navegador headless)
│ ├── threads_extractor.py # Playwright + intercepción de GraphQL
│ └── threads.js
├── tests/
│ └── routing_checks.py # Tests del enrutado + diagnóstico de errores YouTube
├── scripts/
│ └── hooks/pre-commit # Hook que estampa la versión (copiar a .git/hooks/)
├── mobile/ # App Android (cáscara Capacitor sobre la URL de Railway)
│ ├── capacitor.config.json # server.url apunta a Railway → la web no requiere recompilar
│ ├── build-apk.bat # Genera el APK de debug
│ ├── android/ # Proyecto Gradle (versionado como respaldo)
│ └── RedesDownloader-debug.apk # APK distribuible (versionado)
├── cookies/ # (no versionado) *.txt de la extensión; se combinan solos
├── home_tunnel.py # Backend residencial: túnel Cloudflare + registro en Railway
├── tunnel.bat # Un clic: app local + túnel (para usar tu PC de backend)
├── index.html # Frontend principal
├── p.ps1 # Script de inicio rápido (PowerShell)
├── p.bat # Script de inicio rápido (CMD, activa .venv)
├── Procfile # Configuración para despliegue
├── requirements.txt # Dependencias (Playwright NO está: queda fuera de Railway)
├── runtime.txt # Versión de Python
└── README.md # Esta documentación| Capa | Componente | Propósito |
|---|---|---|
| Backend | Python 3.11+ | Lenguaje principal del servidor |
| Backend | http.server + socketserver | Servidor HTTP embebido con multithreading |
| Backend | yt-dlp | Biblioteca principal para extracción de videos |
| Backend | imageio-ffmpeg | Gestión de FFmpeg para procesamiento de video |
| Backend | requests | Cliente HTTP para scraping y proxy |
| Backend | threading | Manejo de descargas en segundo plano |
| Frontend | HTML5 + CSS3 | Estructura y estilos modernos |
| Frontend | Vanilla JavaScript | Lógica de cliente (ES6+) |
| Frontend | SweetAlert2 | Modales y notificaciones |
| Frontend | Hls.js | Reproducción de videos HLS (m3u8) |
flowchart TB
subgraph Cliente
UI[index.html + JavaScript]
SWEET[SweetAlert2]
HLS[Hls.js]
end
subgraph Servidor
HTTP[ThreadedHTTPServer]
API[API Endpoints]
TASK[Task Manager]
end
subgraph Extractores
YT[YouTube]
IG[Instagram]
TT[TikTok]
FB[Facebook]
LI[LinkedIn]
X[X/Twitter]
PI[Pinterest]
TW[Twitch]
end
subgraph Core
YTDLP[yt-dlp]
FFMPEG[FFmpeg]
REQ[requests]
end
UI --> HTTP
HTTP --> API
API --> TASK
API --> Extractores
Extractores --> YTDLP
TASK --> YTDLP
YTDLP --> FFMPEG
sequenceDiagram
participant U as Usuario
participant B as Browser
participant S as Servidor
participant Y as yt-dlp
participant F as FFmpeg
U->>B: Pegar URL
B->>S: POST /api/validate
S-->>B: URL válida ✓
B->>S: POST /api/extract
S->>Y: Extraer info
Y-->>S: Metadatos + formatos
S-->>B: JSON: preview_url + download_target{url, mode}
B->>B: Renderizar tarjeta (usa preview_url)
U->>B: Click en "Descargar"
B->>S: GET /api/download_start (manda download_target)
S->>S: Crear thread
alt mode = source
S->>Y: Descargar desde URL original
Y->>F: Fusionar pistas separadas (si las hay)
F-->>S: MP4 con audio ✓
else mode = direct
S->>S: Descarga progresiva del CDN
else mode = gallery
S->>S: Empaquetar en ZIP
end
loop Progreso
B->>S: GET /api/download_status
S-->>B: {progress: 45%}
end
S-->>B: Redirigir a archivo
B->>U: Iniciar descarga
| Endpoint | Descripción |
|---|---|
/ |
Servir index.html |
/api/download_start?url=...&filename=... |
Iniciar tarea de descarga |
/api/download_cancel?id=task_id |
Cancelar descarga |
/api/download_status?id=task_id |
Consultar estado |
/api/download_file?id=task_id |
Descargar archivo completado (&inline=1 lo sirve para reproducir en vez de bajar; soporta Range/206 para adelantar) |
/api/download?url=...&filename=... |
Descarga directa legacy |
/admin/cookies |
Panel para actualizar la cookie compartida (requiere ADMIN_SECRET) |
| Endpoint | Descripción |
|---|---|
/api/validate |
Validar formato de URL |
/api/extract |
Extraer información de video |
/api/admin/cookies |
Actualizar la cookie compartida (requiere secret = ADMIN_SECRET) |
{
"success": true,
"title": "Video Title",
"uploader": "Channel Name",
"thumbnail": "https://...",
"preview_url": "https://...mp4",
"download_target": {
"url": "https://www.youtube.com/watch?v=...",
"mode": "source"
},
"video_url": "https://...mp4",
"video_quality": "1080p",
"duration": 120,
"filesize": "50.5 MB",
"platform": "YouTube"
}preview_url vs download_target (importante): son dos cosas distintas a
propósito. preview_url es la rendición del CDN que se usa para previsualizar
en la tarjeta; download_target es lo que se descarga. Confundirlos es lo que
producía videos mudos: si se descargaba la rendición del CDN tal cual, en las
plataformas que sirven video y audio como pistas separadas el archivo salía sin
sonido.
mode |
Qué hace | Cuándo |
|---|---|---|
source |
Pasa la URL original a yt-dlp: si hay pistas separadas las fusiona con FFmpeg; si ya viene muxeado lo deja intacto | YouTube, TikTok, Twitch, Facebook (cuando yt-dlp resuelve) |
direct |
Descarga progresiva directa del CDN/servicio externo | Fallbacks manuales, fdown, getmyfb, CDN ya muxeado |
gallery |
Lista de URLs empaquetada en ZIP | LinkedIn/Pinterest con galerías de imágenes |
El frontend normaliza siempre vía normalizeDownloadTarget() /
getDownloadTarget() en src/common/card.js, que además acepta el formato
viejo (original_url suelto) para no romper respuestas previas.
| Protección | Descripción |
|---|---|
| SSRF Protection | Validación de protocolo (solo http/https) |
| Path Traversal | Validación de rutas con commonpath |
| Hostname Blocklist | Bloqueo de IPs privadas locales |
| CORS | Headers configurables por origen |
- Python 3.11+: Asegúrate de tener Python instalado y agregado al PATH.
- FFmpeg: Necesario para unir video y audio en alta calidad.
- Windows: Descargar de ffmpeg.org y agregar
binal PATH.
- Windows: Descargar de ffmpeg.org y agregar
# Clonar/Descargar el repositorio
cd c:\www\video-redes
# Crear entorno virtual
python -m venv .venv
# Activar entorno (PowerShell)
.venv\Scripts\Activate.ps1
# Instalar dependencias
pip install -r requirements.txt
# Instalar el hook que estampa la versión en cada commit
copy scripts\hooks\pre-commit .git\hooks\pre-commitEl hook hay que instalarlo en cada clon. Git nunca versiona
.git/hooks/, así que un clon nuevo arranca sin él y los commits salen con la versión vieja sin avisar. Por eso el fuente vive enscripts/hooks/y se copia a mano.Ojo con la zona horaria si lo tocás: el hook usa
datea secas porque el reloj de la máquina ya está en hora argentina. No le pongasTZ='America/Argentina/Buenos_Aires': Git Bash en Windows no trae tzdata, cae a UTC en silencio y estampa 3 horas adelantado.
Opción A: Solo local (sin backend residencial)
.\p.ps1Opción B: Como backend residencial de Railway (recomendado)
.\tunnel.batEsto levanta la app local y abre el túnel Cloudflare que la registra en
Railway. Con esto andando en tu PC, el contenido con login (Instagram/Facebook)
y Threads funciona desde la URL pública de Railway (celular incluido).
Requiere cloudflared instalado (winget install Cloudflare.cloudflared) y la
variable ADMIN_SECRET (la misma clave del panel admin de Railway).
- Abre tu navegador en
http://localhost:8000(o la URL de Railway) - Pega el enlace de la red social
- El sistema detectará automáticamente la plataforma
- Haz clic en "Buscar" para ver la vista previa
- Haz clic en la tarjeta para descargar
- Exportá tus cookies con la extensión "Get cookies.txt LOCALLY" desde tu Chrome logueado en Instagram/Facebook/Threads.
- Soltá los archivos tal cual (
www.instagram.com_cookies.txt,www.facebook.com_cookies.txt,www.threads.com_cookies.txt) en la carpetacookies/. La app combina todos los.txten uno solo automáticamente (src/common/cookies_util.py); ante duplicados gana el archivo más nuevo. - La carpeta
cookies/está en.gitignore— nunca se sube a GitHub.
El proyecto está configurado para desplegarse fácilmente ("Deploy Ready").
Archivos Clave:
Procfile:web: python src/server.pyruntime.txt:python-3.11requirements.txt: Lista de librerías necesarias- Railway usa Railpack como builder. Con solo
Procfile+requirements.txt+runtime.txtalcanza; no requiere config de build extra.
Pasos para Railway:
- Sube tu código a GitHub
- Crea nuevo proyecto en Railway desde GitHub
- Railway detectará el
Procfiley desplegará automáticamente
Instagram en Railway: el servidor no tiene navegador ni filesystem persistente, así que el contenido que exige login necesita una cookie compartida (una sola sesión de Instagram usada para todos los visitantes — nadie inicia sesión con su propia cuenta).
Configuración inicial (una vez):
- Codificar el archivo local en Base64 (PowerShell):
[Convert]::ToBase64String([IO.File]::ReadAllBytes("cookies\instagram.txt")) | Set-Clipboard
- En Railway → tu proyecto → Variables → crear
INSTAGRAM_COOKIES_B64y pegar el valor (queda en el portapapeles). - Al arrancar,
server.pydecodifica esa variable a un archivo temporal y configuraINSTAGRAM_COOKIES_FILEautomáticamente.
Panel admin para renovarla sin volver a tocar Railway:
- Configurar la variable
ADMIN_SECRET(una contraseña elegida por vos) en Railway. - Entrar a
https://tu-app.up.railway.app/admin/cookies, poner la clave, pegar el contenido nuevo decookies/instagram.txty guardar. Se actualiza al instante, sin reiniciar el server. - Sin
ADMIN_SECRETconfigurada, el panel queda deshabilitado (403) — no expone nada si no lo activás.
Refresco automático: el servidor usa la cookie compartida cada 6 horas en segundo plano para extender su vigencia. Esto no la hace eterna — Instagram igual la vence eventualmente — pero reduce cuánto hay que estar pendiente de renovarla a mano.
Sin cookie configurada, en Railway solo funcionan los reels públicos (vía proxy de embeds); el resto necesita la cookie compartida.
Nota sobre login automático (descartado): se probó un login en vivo con Playwright (navegador automatizado en el server) para refrescar la cookie sin exportar/pegar a mano. No funciona: Instagram/Facebook detectan el navegador automatizado (reCAPTCHA con loop infinito de captchas) y además desconfían de la IP de datacenter de Railway. La única vía confiable es cargar una cookie nacida en un navegador real y logueado (IP residencial) vía la extensión "Get cookies.txt LOCALLY" y pegarla en
/admin/cookies.
Hacer clic en la preview reproduce el video dentro de la propia tarjeta. Descargar es responsabilidad exclusiva del botón de descarga.
Por qué se descarga al temporal antes de reproducir (y no se apunta el
<video> directo al preview_url, que sería instantáneo):
El
preview_urles la rendición del CDN, y en las plataformas que sirven video y audio como pistas separadas puede venir sin audio — el mismo motivo por el que las descargas salían mudas. Reproducirlo directo mostraría el video en silencio sin que nadie se entere. El archivo temporal ya pasó por yt-dlp+FFmpeg, así que tiene audio garantizado.
A diferencia de la descarga, la reproducción no abre modal de progreso: solo un spinner discreto sobre la miniatura mientras se prepara el archivo.
Detalles que hacen que funcione de verdad:
/api/download_fileacepta&inline=1→ respondeContent-Disposition: inlineen vez deattachment, para que el navegador reproduzca en lugar de bajar.- El endpoint soporta Range requests (
206 Partial Content+Accept-Ranges). Sin esto no se puede arrastrar la barra de tiempo, y Safari/iOS directamente se niega a reproducir. Rango simple únicamente; fuera de rango responde416. - El
<video>usaobject-fit: contain(nocovercomo la miniatura): la tarjeta es 9:16 y un video horizontal quedaría recortado a los costados. - Las galerías no son reproducibles → el clic cae a la descarga ZIP de siempre.
Limitación conocida: yt-dlp puede devolver el video en AV1. Chrome y Edge lo reproducen sin problema; Safari viejo y algunos iOS no. En esos casos el archivo se descarga bien igual, pero el reproductor inline puede quedar en negro.
La app de Android no es una copia de la web: es una cáscara delgada
(Capacitor) que carga el sitio vivo de Railway. En mobile/capacitor.config.json:
"server": { "url": "https://redes-download.up.railway.app" }El mobile/www/index.html es solo un placeholder que dice "Cargando…" mientras
levanta la URL real.
La consecuencia práctica (y la que más confunde):
| Qué cambiaste | ¿Hay que recompilar el APK? |
|---|---|
| HTML / CSS / JS del frontend | ❌ No — la app lo toma de Railway al abrirse |
Extractores, server.py, backend |
❌ No — corre en Railway, no en el teléfono |
Plugins de Capacitor, permisos, capacitor.config.json |
✅ Sí |
Ícono, nombre, versión nativa, AndroidManifest |
✅ Sí |
O sea: pusheás a Railway y el APK ya instalado se actualiza solo. No hay que redistribuir el APK por cada arreglo de la web.
Construir el APK:
cd mobile
.\build-apk.batDeja el resultado en mobile/RedesDownloader-debug.apk (versionado en el repo
como respaldo distribuible).
Gotcha del JDK:
mobile/android/gradle.propertiesfijaorg.gradle.java.homeporque elJAVA_HOMEde la máquina apunta a JDK 17 y este proyecto necesita 21. Apunta aC:/Program Files/Java/latest/jdk-21, que es un enlace que mantiene el instalador de Oracle — no a una ruta con patch fijo tipojdk-21.0.11. Java se auto-actualiza de patch y una ruta fija deja el build roto conJava home supplied is invalid.
- Detecta YouTube Shorts automáticamente
- Prioriza formatos progresivos (video+audio)
- Detecta calidad máxima DASH
- Calcula tamaño estimado
Cadena de extracción (de más rápido a más lento):
- URLs directas de CDN (cdninstagram.com) — sin extracción
- yt-dlp anónimo
- Archivo de cookies
cookies/instagram.txt(recomendado — Instagram exige login para la mayoría de los reels desde 2026) - Proxy de embeds (kkinstagram, estilo InstaFix) — resuelve reels públicos sin login, redirige al mp4 del CDN oficial
- Cookies del navegador Chrome/Edge/Firefox (último recurso; falla si el navegador está abierto, y Chrome moderno usa App-Bound Encryption que yt-dlp no puede desencriptar)
Detecta los errores de autenticación modernos ("empty media response", "login required", "rate-limit").
- Videos (yt-dlp)
- Galerías de imágenes (JSON-LD parsing)
- Documentos PDFs (native document config)
Cadena de extracción:
- yt-dlp (web, luego reintenta con ID de reel y modo móvil si falla)
- yt-dlp con la cookie compartida (
cookies/instagram.txt) — resuelve reels/videos que exigen login, incluidosfacebook.com/reel/... - Scraping manual: og:video + playable_url (web y móvil)
- Servicio externo fdown.net
- Servicio externo getmyfb.com (endpoint
/process) — resuelve enlacesshare/v/que exigen login y devuelve el título real del video
- yt-dlp con formato bestvideo+bestaudio
- yt-dlp con merge_output_format=mp4
- Selección de mejor calidad por resolución
- Soporte para pins de video e imagen
- Clips y VODs via yt-dlp
- yt-dlp NO soporta Threads y el video no está en el HTML (lo carga por GraphQL autenticado).
- Se abre el post en un Chromium headless (Playwright) con la cookie de Meta y se intercepta la respuesta de red buscando
browser_native_hd_url/browser_native_sd_url/video_versions(la URL.mp4del CDN). - Playwright se importa perezosamente: en Railway (sin Playwright) devuelve
needs_remote=Truey el servidor reenvía al backend residencial (tu PC), que sí lo tiene. Por eso Threads siempre corre en tu PC. - Es la red más lenta (~10-15 s por video: abrir navegador + cargar + capturar).
- Causa: Instagram exige login para la mayoría de los reels (comportamiento desde 2026). No es un bug de la app.
- Solución recomendada (permanente): Exportar cookies con la extensión "Get cookies.txt LOCALLY" en Chrome (logueado en Instagram) y guardarlas como
cookies/instagram.txt. Funciona local y en Railway. - Alternativa: Cerrar completamente Chrome/Edge para que yt-dlp lea las cookies del navegador. Nota: Chrome moderno (v127+) cifra las cookies con App-Bound Encryption y yt-dlp puede fallar con "Failed to decrypt with DPAPI" — en ese caso la extensión es la única vía.
- Las cookies caducan: si el error reaparece tras semanas, re-exportar el archivo.
- Causa: Los enlaces
facebook.com/share/v/...suelen exigir login. - Solución: Ya está cubierto — el extractor cae automáticamente en getmyfb.com como último recurso. Si aun así falla, el video puede ser privado o de un grupo cerrado.
- Solución: Ya está parchado internamente. El sistema usa
twimg.compara evitar el bloqueo.
- Solución: Instala FFmpeg y agrégalo a tus variables de entorno.
- Causa: El video se descarga primero a tu carpeta temporal y luego se te envía.
- Normalidad: Es el comportamiento esperado para garantizar que el archivo final esté limpio.
Esta herramienta ha sido creada con fines educativos y de uso personal.
- Respeta los derechos de autor y la propiedad intelectual.
- No descargues ni redistribuyas contenido privado sin consentimiento.
- El usuario es responsable del uso que le dé a esta herramienta.
- yt-dlp actualizado a 2026.6.9 (corrige el aviso "version older than 90 days") junto con todas las dependencias.
pydubeliminada (sin uso e incompatible con Python 3.13+). - Instagram: nueva cadena de fallbacks — archivo de cookies (
cookies/instagram.txt) + proxy de embeds (kkinstagram) para reels públicos sin login. Detección de los errores de autenticación modernos. - Facebook: nuevo fallback getmyfb.com para enlaces
share/v/que exigen login. - Nombres de archivo unificados:
plataforma_titulo_fecha.exten las 8 plataformas (helperbuildFilename()compartido). - Resolución robusta de yt-dlp:
src/common/ytdlp_cmd.pylocaliza el ejecutable (venv → PATH → módulo Python); los extractores ya no dependen del PATH. - server.py: lógica de reintentos de Instagram unificada en
download_with_instagram_auth()(archivo de cookies → Chrome → Edge → Firefox). - p.bat ahora activa el entorno virtual antes de arrancar (igual que p.ps1).
- server.log fuera del control de versiones (queda solo local).
- Panel admin (
/admin/cookies, protegido porADMIN_SECRET) para actualizar la cookie compartida sin tocar Railway/terminal. - Refresco automático de sesión: la cookie compartida se usa cada 6 horas en segundo plano para extender su vigencia.
- Cookies compartidas vía
INSTAGRAM_COOKIES_B64: soporte para pasar el archivo de cookies como variable de entorno Base64 en despliegues sin filesystem persistente (Railway). - Facebook: nuevo reintento con la cookie compartida antes de rendirse, arregla
facebook.com/reel/...que exigen login.
- Arquitectura híbrida (Railway + backend residencial): Railway reenvía el contenido con login (Instagram/Facebook) y Threads a tu PC vía Cloudflare Tunnel, porque Meta bloquea las IPs de datacenter (HTTP 400 aun con cookie válida). Tu PC lo resuelve con IP residencial. Una sola URL pública para todos. Ver sección "Arquitectura híbrida".
- Threads: soporte nuevo vía navegador headless (Playwright) que intercepta el GraphQL de Meta. Corre solo en tu PC (Playwright no está en Railway). Logo agregado.
- Cookies multi-archivo: la app combina TODOS los
.txtdecookies/(soporta los per-dominio de la extensión:www.instagram.com_cookies.txt, etc.). Ante duplicados gana el más nuevo. - Reparación de cookies pegadas:
/admin/cookiesarregla tabs→espacios (causa de "empty media response" falsos). home_tunnel.py+tunnel.bat: un clic levanta la app + túnel y la registra en Railway (re-registra cuando la URL del quick tunnel cambia).- Playwright eliminado de Railway: el login-en-vivo automatizado se descartó (Instagram/Facebook lo bloquean con reCAPTCHA); la carga de cookies es por pegado/archivo.
- Audio arreglado en todas las plataformas: se separó
preview_url(lo que se previsualiza) dedownload_target {url, mode}(lo que se descarga). Antes, las plataformas que sirven video y audio como pistas separadas se descargaban copiando la rendición del CDN tal cual → el archivo salía mudo. Ahora el modosourcepasa la URL original a yt-dlp, que fusiona las pistas con FFmpeg y deja intacto lo que ya viene muxeado. Los fallbacks directos (fdown, getmyfb, CDN) y el ZIP de galerías siguen igual. - Facebook: reintento de descarga con la cookie combinada.
- Tests:
tests/routing_checks.pycubre el enrutado de los tres modos. - Nuevo esquema de versión: el header pasó de un contador (
v.4,v.58) a un timestampv.YY.MMDD build(HH.MM)en hora argentina, en dos líneas (nombre grande arriba, versión abajo). Así se ve de un vistazo cuándo se cortó el build que está vivo en Railway, no solo que cambió. - Hook
pre-commit: estampa la versión y re-stageaindex.htmlen cada commit, para que el sello viaje dentro de ese mismo commit. El fuente versionado está enscripts/hooks/pre-commit; se instala copiándolo a.git/hooks/(ver "Instalación"). .codegraph/ignorado: índice local por máquina, nunca se comparte entre checkouts.- YouTube: se dejó de ocultar el motivo del fallo. El fallback de formato básico
capturaba el
stderrde yt-dlp, lo imprimía y lo descartaba, devolviendo el mismo texto genérico para todo: bloqueo por IP de datacenter, video privado, rate-limit o timeout se veían idénticos. Peor:_looks_login_gated()decide si reenviar al backend residencial leyendo ese error, así que nunca reconocía los fallos de YouTube y el reenvío —que ya existía y funcionaba— jamás se activaba. Ahora elstderrcrudo viaja endetaily la causa reconocida se anexa alerror. - Build de Android arreglado:
gradle.propertiesapuntaba ajdk-21.0.11, que dejó de existir cuando Java se auto-actualizó a21.0.12. Ver "App Android (APK)". - Reproductor en la tarjeta: el clic sobre la preview ahora reproduce el video ahí mismo en vez de descargarlo; descargar quedó como responsabilidad exclusiva del botón. Ver "Reproductor inline".
Sobre el timestamp: no se puede meter la fecha/hash de un commit dentro de ese commit — el hash se calcula sobre el contenido, así que escribirlo adentro lo cambia. El hook estampa justo antes de crear el commit; la diferencia es de segundos y alcanza de sobra para confirmar qué build está desplegado.
Documentación actualizada: Agosto 2026
📧 Contacto: julio.corbaz@gmail.com
🌐 Página Web Oficial: https://redes-download.up.railway.app/
Versión del proyecto: v.YY.MMDD build(HH.MM) — ver el header de la app