Onet De-Baiter

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

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

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

})();