Onet De-Baiter

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

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

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

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

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

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

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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
    });

})();