Files

178 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
`./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`).
## Libros de texto
Además del manga, lee libros en EPUB con el mismo lector (hoja que gira, sombra, hendidura, sonido):
- **Project Gutenberg**: dominio público, con búsqueda en su catálogo OPDS.
- **Wikisource en español**: novelas, con el EPUB que genera ws-export.
- **Mis libros**: los EPUB que sube cada usuario (hasta 100 MB), visibles solo para quien los sube.
El servidor convierte el EPUB en una lista cerrada de bloques (`app/books.js`), sin pasar HTML del libro al
navegador, y guarda el resultado en `data/books/`. El navegador maqueta el libro entero como texto continuo según su
pantalla y dibuja cada página en un canvas. La letra, los márgenes y el papel se eligen en el lector, por dispositivo.
Un libro se puede guardar entero para leerlo sin conexión. En Ajustes → «Qué mostrar», cada usuario elige ver manga,
libros o ambos.
## 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
La ficha de cada manga incluye 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, enlaces oficiales y la
imagen de fondo de la obra.
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, una vez por manga y para todos los usuarios: lo guardado se muestra sin
consultar nada y, si es antiguo (info 30 días, fechas 1 día), se actualiza en segundo plano. Los mangas que
sigue algún usuario se completan y se mantienen al día solos, aunque nadie abra su ficha.
La imagen de fondo de la ficha se reescala con Real-ESRGAN (×4 si mide menos de 1000 px de ancho, ×2 si no). Si la obra
no tiene, se usa uno de tus recortes de ese manga; sin recortes, la ficha va sin imagen de fondo.
En la ficha solo quedan a la vista «Continuar/Empezar a leer» y «Capítulos». El botón **⋯** abre el menú lateral con
seguir, listas, recortes, buscar en otras fuentes, dejar de leer, la copia local, el reescalado HD, «En este dispositivo»
y los datos de la obra (autores, editorial, publicación…).
## 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` (por defecto `./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.