OLX Detailed Ratings

Shows detailed ratings for: olx.ro, olx.bg, olx.ua, olx.pt, and olx.pl

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         OLX Detailed Ratings
// @name:ro      OLX Detalii Ratinguri
// @name:bg      OLX Подробни Оценки
// @name:ua      OLX Детальні Оцінки
// @name:pt      OLX Avaliações Detalhadas
// @name:pl      OLX Szczegółowe Oceny
// @description  Shows detailed ratings for: olx.ro, olx.bg, olx.ua, olx.pt, and olx.pl
// @description:ro Detalii ratinguri pentru: olx.ro, olx.bg, olx.ua, olx.pt și olx.pl
// @description:bg Подробни оценки за: olx.ro, olx.bg, olx.ua, olx.pt и olx.pl
// @description:ua Детальні оцінки для: olx.ro, olx.bg, olx.ua, olx.pt та olx.pl
// @description:pt Mostra avaliações detalhadas para: olx.ro, olx.bg, olx.ua, olx.pt e olx.pl
// @description:pl Pokazuje szczegółowe oceny dla: olx.ro, olx.bg, olx.ua, olx.pt i olx.pl
// @author       NWP
// @namespace    https://greasyfork.org/users/877912
// @version      0.2
// @license      MIT
// @match        *://www.olx.ro/*
// @match        *://www.olx.bg/*
// @match        *://www.olx.ua/*
// @match        *://www.olx.pt/*
// @match        *://www.olx.pl/*
// @grant        none
// @run-at       document-end
// ==/UserScript==

(function () {
    "use strict";

    let debug = false;

    let globalScore = null;
    let maxScore = null;
    let ratingsCount = null;
    let user_score_data = null;
    let translationRatings = null;
    let lastCapturedTranslations = null;
    let lastCapturedConfig = null;
    let retriesCount = 0;
    const maxRetries = 50;

    // window.__PAGE_TRANSLATIONS is used for product page
    // window.__INIT_CONFIG__ is used for user page

    const isEmptyObject = (obj) => {
        return obj && Object.keys(obj).length === 0 && obj.constructor === Object;
    };

    const log = (...args) => {
        if (debug) {
            console.log(...args);
        }
    };

    const captureData = () => {
        try {
            if (window.__PAGE_TRANSLATIONS__ && window.__PAGE_TRANSLATIONS__ !== lastCapturedTranslations) {
                lastCapturedTranslations = window.__PAGE_TRANSLATIONS__;
                log('Captured window.__PAGE_TRANSLATIONS__:', lastCapturedTranslations);
                handleWindowVariable();
            }

            if (window.__INIT_CONFIG__ && window.__INIT_CONFIG__ !== lastCapturedConfig) {
                lastCapturedConfig = window.__INIT_CONFIG__;
                log('Captured window.__INIT_CONFIG__:', lastCapturedConfig);
                handleWindowVariable();
            }

            if (!translationRatings || Object.keys(translationRatings).length === 0) {
                if (retriesCount < maxRetries) {
                    retriesCount++;
                    log(`Translation data not valid yet, retrying... (${retriesCount}/${maxRetries})`);
                    setTimeout(captureData, 100);
                } else {
                    console.error('Max retries reached. Translation data could not be retrieved.');
                }
            }
        } catch (error) {
            console.error('Error in captureData:', error);
        }
    };

    const originalFetch = window.fetch;

    window.fetch = async (...args) => {
        try {
            const requestUrl = args[0];
            log('Making API request with URL:', requestUrl);

            if (requestUrl.startsWith("https://khonor.eu-sharedservices.olxcdn.com/api/olx/") &&
                requestUrl.includes("/score/rating")) {

                try {
                    const response = await originalFetch(...args);
                    log('API response received:', response);

                    const clonedResponse = response.clone();

                    clonedResponse.json().then((rating_data) => {
                        log('Rating data received:', rating_data);
                        user_score_data = rating_data.body[0];
                        globalScore = rating_data.body[0].data.score;
                        maxScore = rating_data.body[0].data.range.max;
                        ratingsCount = rating_data.body[0].data.ratings;
                        checkAndUpdateSentimentText();
                    }).catch((error) => {
                        console.error("Error reading response body:", error);
                    });

                    return response;
                } catch (error) {
                    console.error('Error during fetch interception:', error);
                    return originalFetch(...args);
                }
            } else {
                return originalFetch(...args);
            }
        } catch (error) {
            console.error('Error in custom fetch handling:', error);
            return originalFetch(...args);
        }
    };

    function waitForElement(selector, callback, timeout = 10000) {
        try {
            const startTime = Date.now();
            const checkInterval = setInterval(() => {
                try {
                    let element;

                    if (window.location.href.startsWith("https://www.olx.ro/d/oferta")) {
                        const elements = document.querySelectorAll(selector);
                        element = elements.length > 1 ? elements[1] : null;
                    } else if (window.location.href.startsWith("https://www.olx.pl/d/oferta")) {
                        const elements = document.querySelectorAll(selector);
                        element = elements.length > 1 ? elements[1] : null;
                    } else {
                        element = document.querySelector(selector);
                    }

                    if (element) {
                        clearInterval(checkInterval);
                        callback(element);
                    } else if (Date.now() - startTime > timeout) {
                        clearInterval(checkInterval);
                        console.error(`Timeout: Element ${selector} not found within ${timeout}ms`);
                    }
                } catch (error) {
                    console.error('Error in waitForElement interval check:', error);
                    clearInterval(checkInterval);
                }
            }, 100);
        } catch (error) {
            console.error('Error in waitForElement:', error);
        }
    }

    function checkAndUpdateSentimentText() {
        try {
            if (!translationRatings || Object.keys(translationRatings).length === 0) {
                if (retriesCount < maxRetries) {
                    retriesCount++;
                    log(`Translation data not ready yet. Deferring update... (${retriesCount}/${maxRetries})`);
                    setTimeout(checkAndUpdateSentimentText, 100);
                } else {
                    console.error('Max retries reached. Could not update sentiment text.');
                }
                return;
            }
            updateSentimentText();
        } catch (error) {
            console.error('Error in checkAndUpdateSentimentText:', error);
        }
    }

    function updateSentimentText() {
        try {
            const currentUrl = window.location.href;
            let sentimentSpanSelector;

            if (currentUrl.startsWith("https://www.olx.ro/d/oferta/")) {
                sentimentSpanSelector = 'div.css-1k5snlb';
            } else if (currentUrl.startsWith("https://www.olx.ro/oferte/")) {
                sentimentSpanSelector = 'div.css-1k5snlb';
            } else if (currentUrl.startsWith("https://www.olx.pl/d/oferta/")) {
                sentimentSpanSelector = 'p.css-1usyphe';
            } else if (currentUrl.startsWith("https://www.olx.pl/oferty/")) {
                sentimentSpanSelector = 'p.css-1usyphe, p.css-1omjrm';
            } else {
                sentimentSpanSelector = 'span[data-testid="sentiment-title"]';
            }

            waitForElement(sentimentSpanSelector, (sentimentSpan) => {
                try {
                    log('Found sentiment span:', sentimentSpan);

                    if (sentimentSpan && globalScore !== null && sentimentSpan.dataset.scoreUpdated !== "true") {
                        sentimentSpan.style.fontWeight = "bold";
                        sentimentSpan.style.color = "white";
                        sentimentSpan.dataset.scoreUpdated = "true";
                        const score = user_score_data.data.score;
                        const bucketSpec = user_score_data.bucketSpec;
                        const foundRange = bucketSpec.find((bucket) => score >= bucket.range.min && score <= bucket.range.max);
                        if (foundRange) {
                            const scoreContainer = document.createElement("div");
                            scoreContainer.style.display = "flex";
                            scoreContainer.style.flexDirection = "column";
                            scoreContainer.style.alignItems = "flex-start";
                            scoreContainer.style.marginTop = "0.3125rem";

                            if (currentUrl.startsWith("https://www.olx.pl/")) {
                                const oldRatingLabel = document.createElement("span");
                                oldRatingLabel.id = "old_rating";
                                oldRatingLabel.style.fontSize = "0.938rem";
                                oldRatingLabel.style.fontWeight = "bold";
                                oldRatingLabel.style.color = "white";
                                oldRatingLabel.textContent = `Stara ocena: ${translationRatings[user_score_data.data.label]}`;

                                scoreContainer.appendChild(oldRatingLabel);
                            }

                            const inlineContainer = document.createElement("div");
                            inlineContainer.style.display = "flex";
                            inlineContainer.style.alignItems = "center";

                            const fullRangeComparison = `[${foundRange.range.min} - ${score} - ${foundRange.range.max}]`;
                            const scoreText = document.createElement("span");
                            scoreText.id = "score";
                            scoreText.textContent = `[${globalScore}/${maxScore}] ${fullRangeComparison}`;
                            scoreText.style.fontSize = "0.875rem";
                            scoreText.style.fontWeight = "bold";
                            scoreText.style.color = "white";

                            inlineContainer.appendChild(scoreText);

                            appendSVG(inlineContainer);

                            scoreContainer.appendChild(inlineContainer);

                            const totalRatingsElement = document.querySelector('span[data-testid="total-ratings"]');

                            if (totalRatingsElement !== null) {
                                totalRatingsElement.style.fontSize = "0.875rem";
                                totalRatingsElement.style.fontWeight = "bold";
                                totalRatingsElement.style.color = "white";
                            } else {
                                const totalRatingsSpan = document.createElement("span");
                                totalRatingsSpan.id = "total_rating";
                                totalRatingsSpan.style.fontSize = "0.875rem";
                                totalRatingsSpan.style.fontWeight = "bold";
                                totalRatingsSpan.style.color = "white";

                                let language = document.querySelector('meta[http-equiv="content-language"]')?.getAttribute("content");
                                if (language == "pl") {
                                    totalRatingsSpan.textContent = `(${ratingsCount} ${ratingsCount == 1 ? "stara ocena" : "stare oceny"})`;
                                } else if (language == "ro"){
                                    totalRatingsSpan.textContent = `(${ratingsCount} ${ratingsCount == 1 ? "rating vechi" : "ratinguri vechi"})`;
                                } else {
                                    totalRatingsSpan.textContent = `(${ratingsCount} ${ratingsCount == 1 ? "rating" : "ratings"})`;
                                }

                                scoreContainer.appendChild(totalRatingsSpan);
                            }

                            sentimentSpan.after(scoreContainer);
                        }
                    } else {
                        log('Sentiment span not found, globalScore is null, or score already updated');
                    }
                } catch (error) {
                    console.error('Error in waitForElement callback (updateSentimentText):', error);
                }
            });
        } catch (error) {
            console.error('Error in updateSentimentText:', error);
        }
    }

    function appendSVG(inlineContainer) {
        try {
            const svgNamespace = "http://www.w3.org/2000/svg";
            const svgElement = document.createElementNS(svgNamespace, "svg");
            svgElement.setAttribute("width", "1.2rem");
            svgElement.setAttribute("height", "1.2rem");
            svgElement.setAttribute("viewBox", "0 0 24 24");
            svgElement.setAttribute("fill", "currentColor");
            svgElement.id = "rating_legend_tooltip";
            svgElement.style.marginTop = "0.05rem";
            svgElement.style.marginLeft = "0.3125rem";
            svgElement.style.cursor = "pointer";
            svgElement.style.transform = 'rotate(180deg)';

            const path = document.createElementNS(svgNamespace, "path");
            path.setAttribute("d", "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8zm-1-13h2v6h-2zm0 8h2v2h-2z");
            svgElement.appendChild(path);

            inlineContainer.appendChild(svgElement);

            const tooltip = document.createElement("div");
            tooltip.className = "tooltip";
            tooltip.style.position = "absolute";
            tooltip.style.display = "none";
            tooltip.style.border = "1px solid #ccc";
            tooltip.style.backgroundColor = "#f9f9f9";
            tooltip.style.padding = "0.625rem";
            tooltip.style.zIndex = "1000";
            document.body.appendChild(tooltip);

            let tooltipVisible = false;

            svgElement.addEventListener("click", (event) => {
                try {
                    event.stopPropagation();
                    tooltipVisible = !tooltipVisible;
                    if (tooltipVisible) {
                        showTooltip(event, tooltip, svgElement);
                    } else {
                        hideTooltip(tooltip);
                    }
                } catch (error) {
                    console.error('Error in SVG click event (appendSVG):', error);
                }
            });

            document.addEventListener("click", (event) => {
                try {
                    if (!svgElement.contains(event.target) && tooltipVisible) {
                        hideTooltip(tooltip);
                        tooltipVisible = false;
                    }
                } catch (error) {
                    console.error('Error in document click event (appendSVG):', error);
                }
            }, true);

            svgElement.style.fill = 'white';

            svgElement.addEventListener('click', function(event) {
                event.preventDefault();
            });

            svgElement.addEventListener('click', function(event) {
                event.stopPropagation();
            });
        } catch (error) {
            console.error('Error in appendSVG:', error);
        }
    }

    function showTooltip(event, tooltip, svgElement) {
        try {
            event.stopPropagation();
            updateTooltipContent(tooltip);
            const svgRect = svgElement.getBoundingClientRect();
            tooltip.style.left = `${svgRect.right + window.scrollX}px`;
            tooltip.style.top = `${svgRect.bottom + window.scrollY}px`;
            tooltip.style.display = "block";
        } catch (error) {
            console.error('Error in showTooltip:', error);
        }
    }

    function updateTooltipContent(tooltip) {
        try {
            let tooltipContent = "";

            if (!translationRatings || Object.keys(translationRatings).length === 0) {
                if (retriesCount < maxRetries) {
                    retriesCount++;
                    log(`Translation data not available, retrying... (${retriesCount}/${maxRetries})`);
                    setTimeout(() => {
                        updateTooltipContent(tooltip);
                    }, 100);
                } else {
                    console.error('Max retries reached. Could not update tooltip content.');
                }
                return;
            }

            user_score_data.bucketSpec.slice().reverse().forEach((bucket) => {
                if (bucket.bucketName !== "none" && bucket.range.min !== null && bucket.range.max !== null) {
                    tooltipContent += `<p style="margin-top: 0.3125rem; margin-bottom: 0.3125rem;"><strong>${translationRatings[bucket.bucketName] || bucket.bucketName}</strong>: ${bucket.range.min} - ${bucket.range.max}</p>`;
                }
            });

            tooltip.innerHTML = tooltipContent;
        } catch (error) {
            console.error('Error in updateTooltipContent:', error);
        }
    }

    function hideTooltip(tooltip) {
        try {
            tooltip.style.display = "none";
        } catch (error) {
            console.error('Error in hideTooltip:', error);
        }
    }

    const handleWindowVariable = () => {
        try {
            let translationsValid = false;

            if (window.__PAGE_TRANSLATIONS__ && !isEmptyObject(window.__PAGE_TRANSLATIONS__)) {
                const ratingDataNames = JSON.parse(window.__PAGE_TRANSLATIONS__);

                if (ratingDataNames.pageTranslations &&
                    ratingDataNames.pageTranslations.adview &&
                    ratingDataNames.pageTranslations.adview["srt.rating.superTitle"] &&
                    ratingDataNames.pageTranslations.adview["srt.rating.goodTitle"] &&
                    ratingDataNames.pageTranslations.adview["srt.rating.fairTitle"] &&
                    ratingDataNames.pageTranslations.adview["srt.rating.poorTitle"]) {

                    translationRatings = {
                        super: ratingDataNames.pageTranslations.adview["srt.rating.superTitle"],
                        good: ratingDataNames.pageTranslations.adview["srt.rating.goodTitle"],
                        fair: ratingDataNames.pageTranslations.adview["srt.rating.fairTitle"],
                        poor: ratingDataNames.pageTranslations.adview["srt.rating.poorTitle"],
                    };
                    log('%cwindow.__PAGE_TRANSLATIONS__ found and valid:', 'color: green;', translationRatings);
                    translationsValid = true;
                } else {
                    console.warn('window.__PAGE_TRANSLATIONS__ found but missing required translation keys.');
                }
            }

            if (!translationsValid && window.__INIT_CONFIG__) {
                const ratingDataNames = JSON.parse(window.__INIT_CONFIG__);
                const locale = ratingDataNames.appConfig.locale;
                translationRatings = {
                    super: ratingDataNames.language.messages[locale]["srt.rating.superTitle"],
                    good: ratingDataNames.language.messages[locale]["srt.rating.goodTitle"],
                    fair: ratingDataNames.language.messages[locale]["srt.rating.fairTitle"],
                    poor: ratingDataNames.language.messages[locale]["srt.rating.poorTitle"],
                };
                log('%cwindow.__INIT_CONFIG__ used as fallback:', 'color: red;', translationRatings);
            }

            checkAndUpdateSentimentText();
        } catch (error) {
            console.error('Error in handleWindowVariable:', error);
        }
    };

    const observer = new MutationObserver((mutationsList, observer) => {
        try {
            for (let mutation of mutationsList) {
                if (mutation.type === 'childList' || mutation.type === 'attributes') {
                    try {
                        captureData();
                    } catch (error) {
                        console.error('Error in captureData during MutationObserver:', error);
                    }
                }
            }

            if (translationRatings && Object.keys(translationRatings).length > 0) {
                log('Translation data captured. Disconnecting observer.');
                observer.disconnect();
            }
        } catch (error) {
            console.error('Error in MutationObserver callback:', error);
        }
    });

    observer.observe(document.documentElement, {
        childList: true,
        subtree: true,
        attributes: true
    });

    document.addEventListener('DOMContentLoaded', () => {
        try {
            captureData();
        } catch (error) {
            console.error('Error on DOMContentLoaded:', error);
        }
    });

    log('OLX Rating Modifier Script Initialized');
})();