trystan4861 / WTL-LAB Parser

// ==UserScript==
// @name         WTL-LAB Parser
// @description  Mejoras para la legibilidad de los capítulos
// @version      2.0.24a
// @copyright    2024, trystan4861 (https://openuserjs.org/users/trystan4861)
// @license      MIT
// @author       trystan4861
// @namespace    https://wtr-lab.com/
// @match        https://wtr-lab.com/*
// @icon         https://wtr-lab.com/images/favicon.png
// @homepageURL  https://openuserjs.org/scripts/trystan4861/WTL-LAB_Parser
// @updateURL    https://openuserjs.org/meta/trystan4861/WTL-LAB_Parser.meta.js
// @downloadURL  https://openuserjs.org/install/trystan4861/WTL-LAB_Parser.user.js
// @grant        GM_addStyle
// ==/UserScript==

// ==OpenUserJS==
// @author       trystan4861
// ==/OpenUserJS==

/* jshint esversion: 11 */
/*
Change Log:
2.0.23 - 27/12/2024:
* New: Procesar multiples comas

2.0.22 - 24/12/2024:
* Refactor
* Fix: procear millones con decimales
2.0.21 - 24/12/2024:
* New: Añadir comprobador de novela para ejecutar parseE y parseW

2.0.20 - 24/12/2024:
* Fix: Arreglar fallo en localeNumber que formateaba erróneamente la parte decimal agregando separadores de miles cuando no debía

2.0.19 - 23/12/2024:
* New: Procear magnitudes de millones y de mil millones

2.0.18 - 23/12/2024:
* Fix: Activar el script cuando no se entra directamene a un capítulo y quedarse en espera hasta detectar un
      capítulo debido a la forma en que funciona wtr-lab.com para cargar el contenido de las páginas por ajax

2.0.17 - 20/12/2024
* Minor changes

2.0.16 - 16/12/2024:
* Fix: Error en localeNumber
*/

(function () {
    'use strict';

    const getNovelName = () => (window.location.href.match(/serie-\d+\/([\w-]+)\/chapter-\d+/) || [])[1] || null;
    const procesarComas = s => s.replace(/\b(\d+(?:,[0-9]{1,2}){2,})\b/g, match => match.replace(/,/g, ', '));
    const procesarPuntos = s => s.replace(/\b(\d+(?:\.\d+){2,})\b/g, match => match.replace(/\./g, ', '));
    const fixNumber = s => /^\d+\.\d{2}\.000$/.test(s) ? localeNumber(Number(s.replace(/\./g, '').slice(0, -3)) * 10**2) : s;
    const localeNumber = s => String(s).replace(/(\d+)([,.]\d+)?/g, (_, intPart, decimalPart) => intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ".") + (decimalPart || ""));
    const parseW = s => s.replace(/(\d+[\.,]?\d*)[wW](?=\b|\.|\+|$)/g, (_, n) => `${parseFloat(n.replace(',', '.')) * 10**4}` );
    const parseE = s => s.replace(/(\d+([,|\.]\d+)?)[eE](?![a-zA-Z])/g, (_, n) => parseFloat(n.replace(',', '.') * 10**8));
    const tipoFiltro=s=>config.filtros.toBreak.some(f => s.includes(f)) ? "break" : config.filtros.toDelete.some(f => s.includes(f)) ? "delete" : null;
    const parseBillions = texto => texto.replace(/(\d+(?:[,.]\d+)?)(\s*(mil millones))/gi, (_, n, t) => parseFloat(n.replace(',', '.') * 10**3).toFixed(0) + " millones");
    const parseDecimalMillions = (s) => s.replace(/(\d+),(\d+)\s+millones de\s+(.*)$/, (_, entero, decimales, unidad) => `${parseInt(entero + decimales.padEnd(6, '0'), 10)} ${unidad}`);
    const parseMillions = (s) => s.replace(/(\d+),(\d+)\s+millones/, (_, entero, decimales) => localeNumber(entero + decimales.padEnd(6, '0'), 10));
    const quitarEspacioEntreNumeroYW = texto => texto.replace(/(\d+(,\d+)?)(\s)W(?![a-zA-Z0-9])/g, (_, p1) => p1 + "W");
    const getChapterTitle=()=>document.querySelector(".chapter-title")?.innerText;
    const sustituirPalabraSwap = s => s.replace(new RegExp(`(\\w+)\\s+(${Object.keys(store.transforms).join("|")})`, "g"), (_, palabra, termino) => `${store.transforms[termino]} ${palabra}`);
    const sustituirPalabra = s => s.replace(new RegExp(`^(${Object.keys(store.sustitutes).join("|")})(\\W.*)?$`), (_, base, sufijo) => (store.sustitutes[base] || base) + (sufijo || ""));

    const config = {
        delay: 10000,
        estilos: {
            fondoResaltado: "yellow",
            fondoMarcado: "red",
            textoResaltado: "black",
            textoMarcado: "black",
        },
        filtros: {
            toHideEqual: [".", "Xinbiquge"],
            toHideIncludes: [".c0m", ".com"],
            toBreak: ["la versión web es lento", "En el vasto universo, el nacimiento"],
            toDelete: ["window._taboola", "Recordatorio: si descubre que hacer clic"],
        },
        parseW: ["a-miracle-at-the-beginning"],
        parseE: ["a-miracle-at-the-beginning"],
        novels: {
            "a-miracle-at-the-beginning": {
                p:[
                    procesarPuntos,
                    quitarEspacioEntreNumeroYW,
                    parseBillions,
                    parseDecimalMillions,
                    parseMillions,
                    procesarComas,
                ],
                span:[
                    parseW,
                    parseE,
                ],
            }
        },
    };

    const store = {
        ultimaPalabraVisible: null,
        scrollTimeout: null,
        direccionScroll: "down",
        ultimaPosicionScroll: window.scrollY,
        esPrimeraVez: true,
        mutationTimeout: null,
        ultimaPalabraMarcada: null,
        chapterTitle: "",
        sustitutes: {
            RPDC: "corte",
            Yelvzong: "Yelu Zong",
            Yeluzong: "Yelu Zong",
            Yelv: "Yelu",
            Chase: "Perseguidle",
            "【Consejo": "【Anuncio",
            " 】":"】",
        },
        transforms: {
            Shangshu: "Ministro",
        },
    };

    GM_addStyle(`
        .resaltado {
            background-color: ${config.estilos.fondoResaltado};
            color: ${config.estilos.textoResaltado};
        }
        .marked {
            background-color: ${config.estilos.fondoMarcado};
            color: ${config.estilos.textoMarcado};
        }
    `);


    function check2Hide(span) {
        const { toHideIncludes, toHideEqual } = config.filtros;
        if (toHideIncludes.some(t => span.textContent.toLowerCase().includes(t)) ||
            toHideEqual.some(t => span.textContent.trim() === t)) {
            span.style.display = "none";
            return true;
        }
        return false;
    }

    function detectarDireccionScroll() {
        const posicionActual = window.scrollY;
        store.direccionScroll = posicionActual > store.ultimaPosicionScroll ? "down" : "up";
        store.ultimaPosicionScroll = posicionActual;
    }

    const parseAll = s => {
        const match = window.location.href.match(/serie-\d+\/([\w-]+)\/chapter-\d+/);
        if (match) {
            const [_, key] = match;
            if (config.parseE.includes(key)) s = parseE(s);
            if (config.parseW.includes(key)) s = parseW(s);
        }
        return sustituirPalabra(localeNumber(fixNumber(s)));
    };



    function getSpansFrom(parrafo) {
        parrafo.innerHTML = parrafo.textContent.trim()
            .split(" ")
            .map(palabra => `<span class="marcable">${parseAll(palabra)} </span>`)
            .join("");
        return Array.from(parrafo.querySelectorAll("span.marcable"));
    }

    function marcarSpan(span) {
        if (store.ultimaPalabraMarcada) {
            store.ultimaPalabraMarcada.element.classList.remove("marked");
            if (store.ultimaPalabraMarcada.element === span) {
                store.ultimaPalabraMarcada = null;
                return;
            }
        }
        span.classList.add("marked");
        store.ultimaPalabraMarcada = { element: span, tiempoMarcado: Date.now() };
    }

    function main() {
        if (!store.chapterTitle) {
            store.chapterTitle = getChapterTitle();
        }
        if (!store.chapterTitle) return;

        store.esPrimeraVez = true;
        store.ultimaPalabraVisible = null;

        let hideAll = false;
        document.querySelectorAll("p").forEach(parrafo => {
            const filtro = tipoFiltro(parrafo.textContent);
            hideAll = hideAll || filtro === "break";
            if (hideAll || filtro === "delete") {
                parrafo.style.display = "none";
                return;
            }
            parrafo.textContent = parrafo.textContent.trim();
            parrafo.textContent = config.novels[getNovelName()]?.p.reduce((t, f) => f(t), parrafo.textContent) || parrafo.textContent;
            getSpansFrom(parrafo).forEach(span => {
                if (check2Hide(span)) return;
                observer.observe(span);
                span.addEventListener("click", () => marcarSpan(span));
            });
        });

        if (store.ultimaPalabraMarcada && document.contains(store.ultimaPalabraMarcada.element)) {
            store.ultimaPalabraMarcada.element.classList.add("marked");
        }
    }
    const doUltimaVisible =(ultimaVisible)=>{
        store.ultimaPalabraVisible?.classList.remove("resaltado");
        ultimaVisible.classList.add("resaltado");
        store.ultimaPalabraVisible = ultimaVisible;
    }
    const observer = new IntersectionObserver((entradas) => {
        const ultimaVisible = entradas.filter(e => e.isIntersecting).at(-1)?.target;
        if (!ultimaVisible) return;
        if (store.esPrimeraVez) {
            doUltimaVisible(ultimaVisible);
            store.esPrimeraVez = false;
            return;
        }
        if (store.direccionScroll === "down" && ultimaVisible !== store.ultimaPalabraVisible) {
            clearTimeout(store.scrollTimeout);
            store.scrollTimeout = setTimeout(() => doUltimaVisible(ultimaVisible), config.delay);
        }
    }, { threshold: 1.0 });

    const mutationObserver = new MutationObserver(() => {
        const nuevoTitulo = getChapterTitle();
        if (nuevoTitulo !== store.chapterTitle) {
            store.chapterTitle = nuevoTitulo;
            clearTimeout(store.mutationTimeout);
            store.mutationTimeout = setTimeout(main, 100);
        }
    });
    const check=()=>{
        if (getChapterTitle()) {
            console.log("CHECK WTR");
            mutationObserver.observe(document.body, { childList: true, subtree: true });
            window.addEventListener("scroll", detectarDireccionScroll);
            window.addEventListener("load", main);
        }
        else{
            setTimeout(check,2000);
        }
    }
    check();
})();