Background Translator

test

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Background Translator
// @match        https://wtr-lab.com/*/chapter-*?service=web*
// @match        https://translate.google.com/?sl=auto&tl=en&op=translate
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addValueChangeListener
// @description  test
// @version 0.0.1.20260510071109
// @namespace https://greasyfork.org/users/1527990
// ==/UserScript==

(function() {
    'use strict';

    let lastUrl = location.href;
    const TARGET = '.chapter-body';

    const isChineseChapter = (t) => /第\s*\d+\s*章/.test(t) || /[\u4e00-\u9fa5]/.test(t);

// --- GOOGLE TRANSLATE SIDE ---
    if (location.href.includes('translate.google.com')) {
        GM_addValueChangeListener("request_translate", function(name, old_val, new_val) {
            if (!new_val) return;

            // 1. Extract the specific number from the Chinese source (e.g., 86)
            const sourceMatch = new_val.match(/第\s*(\d+)\s*章/);
            const targetNum = sourceMatch ? sourceMatch[1] : null;

            if (!targetNum) {
                console.error("Could not find Chapter Number in source text.");
                return;
            }

            let inputField = document.querySelector('textarea[aria-label="Source text"]');
            if (!inputField) return;

            inputField.focus();
            inputField.value = new_val;
            inputField.dispatchEvent(new Event('input', { bubbles: true }));

            const startTime = Date.now();
            let stabilityCounter = 0;
            let lastFoundText = "";

            let checkResult = setInterval(() => {
                // Get all translation parts
                let parts = document.querySelectorAll('.ryNqvb, span[jsname="W297wb"]');
                if (parts.length === 0) return;

                // The FIRST part should be our Chapter header
                let firstLine = parts[0].innerText.trim();

                // 2. THE FORMAT CHECK: Must be "Chapter [Number]"
                // We use a Regex to ensure "Chapter" is there and the number matches exactly
                const chapterPattern = new RegExp(`^Chapter\\s*${targetNum}`, 'i');
                const isCorrectChapter = chapterPattern.test(firstLine);

                let currentFullText = Array.from(parts).map(p => p.innerText.trim()).filter(t => t.length > 0).join('\n\n');

                // DEBUG LOGGING
                console.log(`Waiting for: Chapter ${targetNum} | Currently seeing: "${firstLine}"`);

                // 3. VALIDATION GATES
                if (isCorrectChapter && currentFullText.length > (new_val.length * 0.3) && currentFullText === lastFoundText) {
                    stabilityCounter++;
                } else {
                    stabilityCounter = 0; // Reset if the number disappears or text flickers
                }

                lastFoundText = currentFullText;

                // Return only after passing all checks for 2 cycles (10 seconds total)
                if (stabilityCounter >= 2) {
                    console.log("MATCH CONFIRMED. Returning Chapter " + targetNum);
                    GM_setValue("receive_translate", currentFullText);
                    GM_setValue("request_translate", "");
                    clearInterval(checkResult);
                } else if (Date.now() - startTime > 60000) {
                    GM_setValue("receive_translate", "Error: Timeout waiting for Chapter " + targetNum);
                    GM_setValue("request_translate", "");
                    clearInterval(checkResult);
                }
            }, 5000);
        });
        return;
    }

// --- HELPER: Get Chapter from URL ---
    const getUrlChapter = () => {
        const match = location.href.match(/chapter-(\d+)/);
        return match ? match[1] : null;
    };

    // --- NOVEL SITE SIDE ---

    function runTranslation() {
        const urlNum = getUrlChapter();
        if (!urlNum) return;

        // 1. Target the specific container for the current chapter
        const container = document.getElementById(`chapter-${urlNum}`) || document.querySelector(`.chapter-container#chapter-${urlNum}`);
        const div = container ? container.querySelector(TARGET) : document.querySelector(TARGET);

        if (!div || div.dataset.status === "processing" || div.dataset.status === "done" || !isChineseChapter(div.innerText)) return;

        div.dataset.status = "processing";

        setTimeout(() => {
            // Verify we haven't clicked 'next' again during the 3s wait
            if (getUrlChapter() !== urlNum) return;

            const lines = div.querySelectorAll('.wtr-line');
            let textArray = [];
            lines.forEach(line => {
                let lineClone = line.cloneNode(true);
                lineClone.querySelectorAll('.para-number-overlay').forEach(e => e.remove());
                let t = lineClone.innerText.trim();
                if (t) textArray.push(t);
            });

            const textToSend = textArray.join('\n\n');
            if (textToSend.length < 10) {
                div.dataset.status = "";
                return;
            }

            div.style.opacity = "0.5";
            GM_setValue("receive_translate", "");
            GM_setValue("request_translate", textToSend);
        }, 3000);
    }

    // --- SPA ENGINE (PRE-LOAD AWARE) ---
    setInterval(() => {
        const currentUrl = location.href;
        const urlNum = getUrlChapter();

        if (currentUrl !== lastUrl) {
            lastUrl = currentUrl;
            console.log("SPA Navigated to chapter:", urlNum);

            // Cleanup: The SPA keeps old chapters, so we remove the OLD overlay
            // but keep the current one if it exists.
            document.querySelectorAll('#ai-overlay').forEach(el => {
                // If the overlay text doesn't match the new chapter number, kill it
                if (!el.innerText.includes(urlNum)) el.remove();
            });
        }

        // Trigger translation
        runTranslation();
    }, 1500);

    // --- HANDLE TRIP BACK ---
    GM_addValueChangeListener("receive_translate", function(n, o, val) {
        if (!val) return;

        // Find the div matching the current URL
        const urlNum = getUrlChapter();
        const container = document.getElementById(`chapter-${urlNum}`);
        const div = container ? container.querySelector(TARGET) : document.querySelector(TARGET);

        if (!div) return;

        // Check if overlay already exists for this specific container
        let overlay = div.parentNode.querySelector('#ai-overlay');
        if (!overlay) {
            overlay = document.createElement('div');
            overlay.id = 'ai-overlay';
            overlay.className = div.className;
            overlay.style.cssText = `
                display: block !important;
                opacity: 1 !important;
                line-height: 1.4 !important;
                word-break: break-word !important;
            `;
            div.parentNode.insertBefore(overlay, div);
        }

        overlay.innerHTML = '';
        const rawLines = val.replace(/waerdygjfkstlhujhgfd/g, 'awdrsgyjekfthuhgf').split('\n');
        let lastLineWasEmpty = false;

        rawLines.forEach(lineText => {
            const trimmed = lineText.trim();
            if (trimmed === '' && lastLineWasEmpty) return;

            const p = document.createElement('div');
            if (trimmed === '') {
                p.style.height = "0.4em";
                lastLineWasEmpty = true;
            } else {
                p.innerText = trimmed;
                p.style.marginBottom = "0.4em";
                lastLineWasEmpty = false;
            }
            overlay.appendChild(p);
        });

        div.style.display = "none";
        div.setAttribute('data-status', 'done');
        GM_setValue("receive_translate", "");
    });
})();