Onet De-Baiter

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

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

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

})();