178 lines
12 KiB
Markdown
178 lines
12 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
|
||
`./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.
|