- Arrastrar con el dedo pasa la página siguiendo el toque (hoja, deslizar o fundido); al soltar termina de pasar o vuelve a su sitio según lo recorrido y el impulso. - Sonido de roce de papel sintetizado en el navegador, al azar entre 6 variantes; volumen o sin sonido en las opciones del lector. - Doble página hacia atrás: se precargan también las 2 páginas anteriores, el lado de las páginas nuevas sale del orden de lectura y, si aún no han cargado, se desliza en vez de pasar la hoja. - Safari: sin líneas entre las tiras de la hoja (sombreado con filtro en la propia cara y más solape). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
152 lines
10 KiB
Markdown
152 lines
10 KiB
Markdown
# Manga Reader
|
||
|
||
Lector web de manga autoalojado, con las fuentes de
|
||
[Tanko](https://github.com/Alexandro521/Tanko) (`src/servers/`) más M440:
|
||
|
||
- **LeerCapitulo** (español, por defecto): mucho más catálogo. El scraper está reescrito para el
|
||
sitio actual y ya no necesita navegador headless (las páginas vienen en el HTML).
|
||
- **MangaDex** (multi-idioma): español = es-la + es combinados. Muchos títulos populares no tienen
|
||
capítulos por licencias; la ficha ofrece buscarlos en LeerCapitulo o M440.
|
||
- **M440** (m440.in, español): búsqueda (mínimo 3 letras), populares de su portada y actualizados
|
||
(paginado); sin categorías, porque ese listado está detrás del desafío de Cloudflare. La lista de
|
||
capítulos viene cifrada: la clave se saca de un script ofuscado del sitio ejecutándolo en un
|
||
proceso aparte sin permisos de archivos ni procesos (`app/m440-key.mjs`) y se vuelve a obtener sola
|
||
si el sitio la cambia.
|
||
- Búsqueda, populares y actualizados recientemente.
|
||
- Lector por página (toques/flechas, izq→der o der→izq) o en vertical, modo ahorro de datos.
|
||
- Usuarios con contraseña; el **historial y la página exacta** se guardan en el servidor
|
||
(`data/manga.db`), así que puedes seguir desde cualquier dispositivo/sesión.
|
||
- **Descargas**: botón "⬇ Descargar" en la ficha. Baja el manga entero en segundo plano a
|
||
`/zdata/media/mangas/<Título>/Capitulo NNNN.cbz` (+ `cover.jpg`, `ComicInfo.xml` en cada CBZ),
|
||
compatible con Kavita/Komga/Mihon. Se reanuda tras reinicios; repetirla baja solo los capítulos
|
||
nuevos. Los capítulos descargados se leen desde disco y, sin conexión, la ficha usa los datos guardados.
|
||
- **Novedades**: los mangas que empiezas a leer (o marcas con "Seguir") se revisan cada
|
||
`CHECK_INTERVAL_HOURS` (3 h). El inicio muestra "N caps. nuevos", la ficha marca NUEVO, los
|
||
mangas con copia local descargan lo nuevo solos, y se puede avisar por ntfy o Telegram (Ajustes).
|
||
- **Lector**: una página, doble página (tablet horizontal/PC; portada y páginas apaisadas van solas)
|
||
o vertical. Modo, ajuste y dirección se recuerdan por manga (por defecto: manhwa/manhua en
|
||
vertical, manga por página de derecha a izquierda). Zoom con pellizco, doble toque en el centro,
|
||
Ctrl+rueda o + / − / 0. Al pasar página la hoja se dobla como papel y, al deslizar, sigue al dedo;
|
||
suena un roce de papel elegido al azar entre 6 variantes sintetizadas en el navegador (volumen o
|
||
sin sonido en las opciones del lector).
|
||
- **Reescalado HD**: en la ficha de un manga descargado, "Reescalar a HD" pasa sus capítulos por
|
||
Real-ESRGAN (`realesr-animevideov3` ×2, en la GPU por Vulkan) y guarda la versión HD en
|
||
`<Título>/.hd/` (carpeta oculta: el CBZ original no se toca y Kavita/Komga no ven duplicados).
|
||
Con el manga marcado, los capítulos que se descarguen después también se reescalan. La lista de
|
||
capítulos marca **HD**, el lector usa la versión HD (se puede desactivar en sus ajustes) y desde
|
||
allí también se reescala un capítulo suelto. En una Intel HD 630 tarda ~13 s por página y cada
|
||
página ocupa ~2,5× más (WebP calidad 85).
|
||
- **Portadas HD**: las portadas de los mangas que sigues o tienes en el historial se reescalan solas en
|
||
segundo plano (×4 las pequeñas, ×2 el resto; ~4 s cada una) y desde entonces se sirven en HD. Las
|
||
de búsquedas y populares se quedan como están. Caché en `data/covers/` (se puede borrar: se regeneran).
|
||
- **Recortes**: en el lector, ✂ y arrastrar sobre un panel lo guarda en "Recortes" con su manga,
|
||
capítulo y página ("Ir a la página" lleva directo allí). Se recorta del original y solo el recorte se
|
||
reescala con Real-ESRGAN (×4, o ×2 si ya mide más de 1200 px; unos segundos). Se guardan en
|
||
`data/clips/` (`<id>.png` original y `<id>-hd.webp`), con nota opcional y descarga.
|
||
- **Colas** (antes "Descargas"): descargas en curso y en espera, cola de reescalado HD por manga
|
||
(capítulo y página en curso, siguientes, tiempo restante, errores con "Reintentar", "Detener"),
|
||
recortes y portadas pendientes, y debajo la biblioteca descargada con su tamaño; todo en tarjetas
|
||
verticales con portada, como el historial.
|
||
- **Librería** (antes "Historial"): pestañas Leyendo, Siguiendo, Terminados, En el dispositivo y
|
||
listas propias (Favoritos, Pendientes, Abandonados y las que crees). Desde la ficha, el botón
|
||
"Listas" añade o quita el manga de cada lista.
|
||
- **Recomendado para ti** (inicio): populares de MangaDex con capítulos en tu idioma dentro de tus 3
|
||
géneros más leídos, sin lo que ya tienes en la librería.
|
||
- **Descarga anticipada** (Ajustes): al pasar la mitad de un capítulo, el servidor baja los N
|
||
siguientes (por defecto 2) a la copia local y, si se marca, los reescala a HD.
|
||
- **App instalable y lectura sin conexión**: manifest + service worker. En la ficha, "En este
|
||
dispositivo" guarda capítulos (páginas incluidas) en el navegador para leerlos sin internet; el
|
||
progreso leído sin red se envía al volver. **Requiere HTTPS** (o `localhost`): por `http://IP:puerto`
|
||
el navegador no permite service workers. Por ejemplo `tailscale serve` o un proxy con certificado.
|
||
- **Estadísticas**: rachas, capítulos y páginas por semana, calendario de actividad, géneros
|
||
favoritos y lo más leído.
|
||
- Las imágenes pasan por el servidor (`/api/img`), solo con URLs firmadas por él (clave en `data/secret`).
|
||
|
||
## Uso
|
||
|
||
```bash
|
||
cp .env.example .env # opcional: ajusta rutas, puerto, usuario…
|
||
docker compose up -d --build
|
||
# http://<ip-del-servidor>:3352
|
||
```
|
||
|
||
El contenedor no depende del sistema operativo: imagen Node fijada (`node:24.22.2-trixie-slim`,
|
||
con Mesa Vulkan y Real-ESRGAN verificado por SHA-256), sin dependencias npm, y todo lo propio de la máquina está en `.env` (ver `.env.example`).
|
||
Sin `.env` arranca igual: puerto 3352 en todas las interfaces y descargas en `./mangas`.
|
||
|
||
El primer usuario puede registrarse siempre; después el registro queda cerrado salvo
|
||
`ALLOW_SIGNUP=true` en `.env`.
|
||
|
||
## Info de la obra y fechas de los capítulos
|
||
|
||
El botón **Info** de cada manga muestra datos de [AniList](https://anilist.co) y
|
||
[MangaUpdates](https://www.mangaupdates.com): autores, revista y editorial, fechas de publicación, tomos,
|
||
puntuaciones, rankings, adaptaciones al anime, obras relacionadas, recomendaciones y enlaces oficiales.
|
||
La obra se identifica por los enlaces de MangaDex; si el manga viene de LeerCapitulo o M440, por su título
|
||
(solo si coincide, para no mostrar datos de otra obra).
|
||
|
||
Las fechas de la lista de capítulos son las de la **primera traducción conocida** de cada capítulo
|
||
(MangaDex en cualquier idioma y MangaUpdates), que en series en curso es muy cercana a la de publicación;
|
||
los sitios en español suelen poner la fecha en que ellos lo subieron. Las marcadas con «≈» son estimadas
|
||
entre dos capítulos con fecha. Todo se guarda en la base de datos (ficha 7 días, fechas 1 día).
|
||
|
||
## Sin internet en el servidor
|
||
|
||
Si el servidor pierde la conexión a internet (pero tú sigues llegando a él por la red local o
|
||
Tailscale), la app pasa sola a **modo sin conexión**:
|
||
|
||
- Arriba aparece el aviso «El servidor no tiene internet». La portada muestra lo **descargado en el
|
||
servidor** con un buscador, y «Continuar leyendo» solo enseña esos mangas.
|
||
- Fichas y capítulos descargados funcionan como siempre, sin esperas. Lo que no está descargado
|
||
avisa al momento en vez de quedarse cargando.
|
||
- Las portadas salen de la copia local o de la caché HD.
|
||
- Las descargas en cola se pausan (no cuentan como fallidas). La comprobación de capítulos nuevos y la
|
||
descarga anticipada esperan.
|
||
- El servidor vuelve a comprobar la conexión cada 20 s. Al volver internet, todo se reanuda solo y
|
||
la portada vuelve a la normalidad.
|
||
|
||
Se detecta cuando una fuente falla por red y ninguno de MangaDex, LeerCapitulo, M440 ni 1.1.1.1
|
||
responde (`app/connectivity.js`).
|
||
|
||
## Exponerlo a internet
|
||
|
||
- **Pon HTTPS delante** (Caddy, Nginx, Cloudflare Tunnel o `tailscale funnel`): sin HTTPS la contraseña
|
||
y la cookie viajan en claro. Con HTTPS la cookie lleva `Secure` y se envía HSTS.
|
||
- Con un proxy inverso, define `REAL_IP_HEADER` en `.env` (`x-forwarded-for` en Caddy/Nginx,
|
||
`cf-connecting-ip` en Cloudflare) para que el baneo vea la IP real. Solo se cree esa cabecera si la
|
||
conexión llega desde una IP privada (el proxy), así que desde internet no se puede falsificar.
|
||
- **Baneo de IP**: 6 intentos fallidos (contraseña incorrecta, registro con el registro cerrado,
|
||
contraseña actual incorrecta) en 60 min bloquean la IP 24 h para toda la web. Ajustable con
|
||
`BAN_MAX_FAILS`, `BAN_WINDOW_MIN`, `BAN_HOURS`; red local, loopback y Tailscale nunca se bloquean.
|
||
En Ajustes se ven las IPs bloqueadas y se pueden desbloquear.
|
||
- Registro cerrado por defecto (`ALLOW_SIGNUP=false`); contraseñas de 8+ caracteres; cambiar la
|
||
contraseña cierra las demás sesiones.
|
||
- Cabeceras: CSP estricta (sin scripts en línea), `X-Frame-Options: DENY`, `nosniff`, `Referrer-Policy`.
|
||
- El proxy de imágenes solo descarga URLs firmadas por el servidor y nunca hacia IPs privadas
|
||
(comprobado también en cada redirección). Los errores internos no se muestran al cliente.
|
||
|
||
## Qué guardar / mover a otro servidor
|
||
|
||
| Qué | Dónde | Contenido |
|
||
|---|---|---|
|
||
| Código | este repositorio | se recrea con `git clone` |
|
||
| `.env` | raíz del proyecto (no está en git) | rutas, puerto, PUID/PGID, URL pública |
|
||
| `data/` | raíz del proyecto (no está en git) | `manga.db` (+ `-wal`/`-shm`): usuarios, historial, estadísticas, seguimiento, ajustes; `secret`: clave del proxy de imágenes; `clips/`: recortes; `covers/`: portadas HD (caché) |
|
||
| Mangas descargados | `MANGA_DIR_HOST` (aquí `/zdata/media/mangas`) | CBZ + portadas |
|
||
|
||
Pasos en el sistema nuevo:
|
||
|
||
```bash
|
||
git clone <repo> manga-reader && cd manga-reader
|
||
# copiar .env y data/ desde el servidor anterior (con el contenedor parado), y la carpeta de mangas
|
||
id -u; id -g # si no son 1000, ponlos en PUID/PGID del .env
|
||
docker compose up -d --build
|
||
```
|
||
|
||
Para el reescalado HD el host necesita `/dev/dri` (GPU Intel/AMD con Mesa) y `RENDER_GID` en `.env`
|
||
con el grupo `render`. Sin GPU, quita `devices`/`group_add` del compose: todo lo demás funciona igual.
|
||
|
||
Si el usuario del host no coincide con PUID/PGID, el contenedor no arranca y el log dice
|
||
exactamente qué `chown` hacer. Si la carpeta de mangas no existe, Docker la crea como root:
|
||
créala antes (`mkdir -p` con tu usuario) para que las descargas funcionen.
|