Fuente M440, modo sin conexión (servidor sin internet y app sin servidor), cola del dispositivo en Colas y arreglo del reescalado de tiras altas
- M440 (m440.in, español): búsqueda, populares, actualizados paginados, ficha, capítulos, descargas y seguimiento. La lista de capítulos viene cifrada: la clave se saca de un script ofuscado del sitio ejecutándolo en un proceso aislado con permisos mínimos (m440-key.mjs) y se renueva sola. - Servidor sin internet (connectivity.js): se detecta en segundos; las fuentes fallan al instante, la portada muestra lo descargado en el servidor con buscador, portadas locales de respaldo, descargas y comprobaciones en pausa y reanudación automática al volver la conexión. - App sin conexión con el servidor: el service worker usa la copia guardada si el servidor no contesta en 3 s, las peticiones comprueban si el servidor sigue vivo en vez de quedarse colgadas, aviso arriba e inicio con lo guardado en el dispositivo; al volver, se reconecta sola y envía el progreso pendiente. - Colas: sección «En este dispositivo» con el guardado en curso en directo y cancelación (sin dejar capítulos a medias); también visible sin servidor. - Imágenes: el proxy y el guardado en el dispositivo reintentan con tiempo límite cuando el CDN de la fuente falla. - Reescalado HD: las páginas que superan 16383 px al reescalar (tiras de webtoon) se reducen al límite de WebP en vez de hacer fallar el capítulo. - Entidades HTML con nombre en las sinopsis; pestañas de fuentes cortas en móvil; portadas rotas con icono de respaldo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+5
-4
@@ -6,7 +6,7 @@ const REC_TTL = 6 * 3600e3
|
||||
import { genreName } from './features.js'
|
||||
const norm = s => String(s).trim().toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '')
|
||||
|
||||
export function setupLibrary({ db, HttpError, json, readBody, MANGA_ID, fetchMangaFull, enqueueDownload, upscale, stats, searchManga, mdCategories }) {
|
||||
export function setupLibrary({ db, HttpError, json, readBody, MANGA_ID, fetchMangaFull, enqueueDownload, upscale, stats, searchManga, mdCategories, net }) {
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS lists (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -54,12 +54,12 @@ export function setupLibrary({ db, HttpError, json, readBody, MANGA_ID, fetchMan
|
||||
// Al pasar la mitad de un capítulo, encola los N siguientes (y, si se pidió, su versión HD).
|
||||
function onProgress(userId, b, page, total) {
|
||||
const cfg = prefetchCfg(userId)
|
||||
if (!cfg.n || !total || page < Math.floor(total / 2)) return
|
||||
if (!cfg.n || !total || page < Math.floor(total / 2) || !net.online()) return
|
||||
const key = `${b.mangaId}|${b.chapterId}|${cfg.n}|${cfg.hd}`
|
||||
if (Date.now() - (recent.get(key) || 0) < 30 * 60e3) return
|
||||
recent.set(key, Date.now())
|
||||
if (recent.size > 2000) recent.delete(recent.keys().next().value)
|
||||
const lang = b.mangaId.startsWith('lc~') ? 'es' : b.lang
|
||||
const lang = /^(lc|mi)~/.test(b.mangaId) ? 'es' : b.lang
|
||||
fetchMangaFull(b.mangaId, lang).then(({ manga, chapters }) => {
|
||||
const i = chapters.findIndex(c => c.id === b.chapterId)
|
||||
if (i < 0) return
|
||||
@@ -82,6 +82,7 @@ export function setupLibrary({ db, HttpError, json, readBody, MANGA_ID, fetchMan
|
||||
async function recommendations(userId, refresh) {
|
||||
const hit = recCache.get(userId)
|
||||
if (hit && !refresh && Date.now() - hit.at < REC_TTL) return hit.data
|
||||
if (!net.online()) return hit?.data ?? { genres: [], items: [] }
|
||||
const lang = db.prepare('SELECT lang FROM users WHERE id = ?').get(userId)?.lang || 'es-la'
|
||||
const genres = stats(userId).genres.slice(0, 5)
|
||||
const cats = (await mdCategories().catch(() => [])).filter(c => c.group === 'Géneros' || c.group === 'Temáticas')
|
||||
@@ -105,7 +106,7 @@ export function setupLibrary({ db, HttpError, json, readBody, MANGA_ID, fetchMan
|
||||
const items = [...score.values()].sort((a, b) => b.pts - a.pts).slice(0, 18)
|
||||
.map(({ m, why }) => ({ ...m, sub: `Por ${why.slice(0, 2).join(' y ')}` }))
|
||||
const data = { genres: picked.map(x => ({ name: x.g.name, id: x.cat.id, source: 'md' })), items }
|
||||
recCache.set(userId, { at: Date.now(), data })
|
||||
if (items.length) recCache.set(userId, { at: Date.now(), data }) // vacío (fuente caída): no guardarlo 6 h
|
||||
return data
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user