Onet De-Baiter

Zamienia tytuły na stronie Onet.pl na mniej click-baitowe

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

You will need to install an extension such as Tampermonkey to install this script.

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Onet De-Baiter
// @version      1.3.4
// @description  Zamienia tytuły na stronie Onet.pl na mniej click-baitowe
// @match        *://www.onet.pl/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @connect      debaiter.westeurope.cloudapp.azure.com
// @namespace https://greasyfork.org/users/1575957
// ==/UserScript==

(function() {
    'use strict';

    // --- KONFIGURACJA ---
    // Selektor elementów, w których skrypt szuka linków i nagłówków H3
    const ARTICLE_SELECTOR = 'article a[href^="https://www.onet.pl/"], article a[href^="https://wiadomosci.onet.pl/"]';

    // --- SEKCJA CSS ---
    // Przeniesione style dla przetworzonych nagłówków
    GM_addStyle(`
        .debaiter-processed {
            text-decoration: underline dotted currentColor !important;
            text-underline-offset: 4px !important;
        }
    `);

    // --- SEKCJA CACHE ---

    // Zwraca zbuforowany tytuł dla danego hasha lub null, jeśli nie istnieje
    async function getFromCache(hash) {
        return GM_getValue(hash, null);
    }

    // Zapisuje nowy tytuł do pamięci podręcznej
    async function saveToCache(hash, title) {
        GM_setValue(hash, title);
    }

    // --- FUNKCJE POMOCNICZE I MODYFIKUJĄCE ---

    // Funkcja wyliczająca SHA-256
    async function sha256(message) {
        const msgBuffer = new TextEncoder().encode(message);
        const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
        const hashArray = Array.from(new Uint8Array(hashBuffer));
        return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
    }

    // Funkcja aktualizująca H3 i dodająca klasę "przetworzony"
    function updateH3(h3Element, newTitle) {
        h3Element.title = h3Element.innerText;
        h3Element.innerText = newTitle;
        // Przypisanie klasy CSS zamiast stylów inline
        h3Element.classList.add('debaiter-processed');
    }

    // --- LOGIKA GŁÓWNA ---

    // Zmienne do grupowania (batching) zapytań
    let pendingPayload = [];
    let pendingElementsMap = new Map();
    let batchTimeout = null;

    // Funkcja wysyłająca zgromadzoną paczkę danych na serwer
    function sendBatch() {
        if (pendingPayload.length === 0) return;

        // Kopiujemy obecny stan do wysyłki i od razu czyścimy globalne zmienne na poczet kolejnych artykułów
        const payloadToSend = [...pendingPayload];
        const mapForThisBatch = new Map(pendingElementsMap);

        pendingPayload = [];
        pendingElementsMap.clear();

        GM_xmlhttpRequest({
            method: "POST",
            url: "https://debaiter.westeurope.cloudapp.azure.com/api/v1/",
            data: JSON.stringify(payloadToSend),
            headers: {
                "Content-Type": "application/json"
            },
            onload: function(response) {
                try {
                    const newTitles = JSON.parse(response.responseText);
                    for (const [hash, newTitle] of Object.entries(newTitles)) {
                        if (mapForThisBatch.has(hash)) {
                            updateH3(mapForThisBatch.get(hash), newTitle);
                            saveToCache(hash, newTitle); // Zapis do cache po otrzymaniu z serwera
                        }
                    }
                } catch (error) {
                    console.error("De-Baiter: Błąd parsowania odpowiedzi", error);
                    console.error(response.responseText);
                }
            },
            onerror: function(error) {
                console.error("De-Baiter: Błąd sieci podczas wysyłania", error);
            }
        });
    }

    // Funkcja szukająca i przetwarzająca nowe elementy na stronie
    async function processElements() {
        // Używamy zmiennej ARTICLE_SELECTOR zdefiniowanej na górze skryptu
        const articles = document.querySelectorAll(ARTICLE_SELECTOR);
        let addedNew = false;

        for (const a of articles) {
            // Zapobiegamy ponownemu przetwarzaniu tego samego artykułu (np. przy scrollowaniu)
            if (a.dataset.debaiterProcessed) continue;

            const h3 = a.querySelector('h3');
            if (h3) {
                const title = h3.innerText.trim();
                if (title && a.href) {
                    // Oznaczamy element jako już znaleziony
                    a.dataset.debaiterProcessed = "true";

                    // Oczyszczamy URL z parametrów śledzących (np. ?srcc=...) dla spójnego hasha
                    const cleanHref = a.href.split('?')[0];
                    const hash = await sha256(cleanHref); // Hash z czystego hrefa

                    // Wstępna obsługa cache - sprawdzamy przed dodaniem do kolejki
                    const cachedTitle = await getFromCache(hash);

                    if (cachedTitle) {
                        updateH3(h3, cachedTitle);
                    } else {
                        pendingElementsMap.set(hash, h3);
                        pendingPayload.push({
                            hash: hash,
                            title: title,
                            href: cleanHref // Wysyłamy czysty href
                        });
                        addedNew = true;
                    }
                }
            }
        }

        // Jeśli znaleziono nowe tytuły (niepochodzące z cache), restartujemy odliczanie (debounce).
        // Zapytanie poleci 500ms po pojawieniu się ostatniego elementu w oknie.
        if (addedNew) {
            if (batchTimeout) clearTimeout(batchTimeout);
            batchTimeout = setTimeout(sendBatch, 500);
        }
    }

    // 1. Uruchomienie dla elementów już istniejących w momencie ładowania
    processElements();

    // 2. Użycie MutationObserver do reagowania na hydrację i doładowywanie przy scrollowaniu
    const observer = new MutationObserver((mutations) => {
        let shouldProcess = false;
        // Sprawdzamy czy modyfikacja DOM polegała na dodaniu nowych węzłów
        for (const mutation of mutations) {
            if (mutation.addedNodes.length > 0) {
                shouldProcess = true;
                break;
            }
        }
        if (shouldProcess) {
            processElements();
        }
    });

    // Podpinamy obserwatora pod całe <body>
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

})();