Greasy Fork

Greasy Fork is available in English.

GreasyFork/SleazyFork 星级评分显示 & 桥接

将视觉上的绿/黄/紅 5 级进度条转换为 0..5 星级评分。对于 SleazyFork,它还将损坏的链接重定向到 GreasyFork。

当前为 2026-05-01 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name            GreasyFork/SleazyFork Star Rating Display & Bridge
// @name:de         GreasyFork/SleazyFork Sterne-Bewertungsanzeige & Brücke
// @name:en         GreasyFork/SleazyFork Star Rating Display & Bridge
// @name:fr         GreasyFork/SleazyFork Affichage der l'évaluation par étoiles & Pont
// @name:zh-CN      GreasyFork/SleazyFork 星级评分显示 & 桥接
//
// @description     Turns the visual 5nd green/yellow/red bar into a 0..5 star rating. For SleazyFork, it also redirects broken links to GreasyFork.
// @description:de  Verwandelt die visuelle 5-stufige grüne/gelbe/rote Leiste in eine 0..5-Sterne-Bewertung. Für SleazyFork werden zudem defekte Links automatisch auf GreasyFork umgeleitet.
// @description:en  Turns the visual 5-level green/yellow/red bar into a 0..5 star rating. For SleazyFork, it also redirects broken links to GreasyFork.
// @description:fr  Transforme la barre visuelle verte/jaune/rouge à 5 niveaux en une évaluation de 0 à 5 étoiles. Pour SleazyFork, il redirige également les liens rompus vers GreasyFork.
// @description:zh-CN 将视觉上的绿/黄/紅 5 级进度条转换为 0..5 星级评分。对于 SleazyFork,它还将损坏的链接重定向到 GreasyFork。
//
// @version         0.0.4
// @author          Wack.3gp (http://greasyfork.icu/users/4792)
// @copyright       2026+, Wack.3gp
// @namespace       http://greasyfork.icu/users/4792
// @license         CC BY-NC-SA-4.0; https://creativecommons.org/licenses/by-nc-sa/4.0/
// @icon            https://www.google.com/s2/favicons?sz=64&domain=greasyfork.org
//
// @match           http://greasyfork.icu/*/scripts*
// @match           http://greasyfork.icu/*/users/*
// @match           https://sleazyfork.org/*/scripts*
// @match           https://sleazyfork.org/*/users/*
//
// @grant           GM_xmlhttpRequest
// @connect         sleazyfork.org
// @run-at          document-end
//
// @supportURL      http://greasyfork.icu/scripts/576222/feedback
// @compatible      Chrome tested with Tampermonkey
// @contributionURL https://www.paypal.com/donate/?hosted_button_id=BYW9D395KJWZ2
// @contributionAmount €1.00
// ==/UserScript==

(function() {
    'use strict';

    const addStyles = function() {
        if (document.getElementById('star-rating-style')) return;
        const style = document.createElement('style');
        style.id = 'star-rating-style';
        style.textContent = `
            .script-list-ratings { 
                display: inline-block !important; 
                min-width: 130px; 
                vertical-align: middle; 
            }
            .rating-link {
                text-decoration: none !important;
                color: inherit !important;
                display: inline-flex !important;
                cursor: pointer !important;
                border: none !important;
                position: relative;
                z-index: 100;
            }
            .rating-box { 
                display: inline-flex; 
                align-items: center; 
                gap: 6px; 
                font-family: sans-serif;
            }
            .rating-num { 
                font-weight: bold; 
                color: #333; 
                font-size: 13px; 
            }
            .star-outer { 
                position: relative; 
                display: inline-block; 
                font-size: 16px; 
                color: #ddd; 
                letter-spacing: 1px;
            }
            .star-outer::before { content: '★★★★★'; }
            .star-inner { 
                position: absolute; 
                top: 0; left: 0; 
                white-space: nowrap; 
                overflow: hidden; 
                color: #f39c12; 
            }
            .star-inner::before { content: '★★★★★'; }
            .rating-total { 
                color: #888; 
                font-size: 12px; 
            }
            .rating-link:hover .rating-num { color: #f39c12; }
            .rating-link:hover .star-outer { filter: brightness(1.1); }
        `;
        document.head.appendChild(style);
    };

    const checkLinkBridge = function(container) {
        if (!window.location.hostname.includes('sleazyfork.org')) return;

        const scriptLink = container.querySelector('.script-link');
        if (!scriptLink || scriptLink.dataset.bridgeChecked) return;
        
        scriptLink.dataset.bridgeChecked = "true";

        GM_xmlhttpRequest({
            method: "HEAD",
            url: scriptLink.href,
            onload: function(response) {
                if (response.status === 404) {
                    const allLinks = container.querySelectorAll('a');
                    allLinks.forEach(a => {
                        const href = a.getAttribute('href');
                        if (href) {
                            if (href.startsWith('/')) {
                                a.href = "http://greasyfork.icu" + href;
                            } else if (href.includes('sleazyfork.org')) {
                                a.href = href.replace('sleazyfork.org', 'greasyfork.org');
                            }
                        }
                    });
                }
            }
        });
    };

    const transformRatings = function() {
        const ratingContainers = document.querySelectorAll('.script-list-ratings');
        
        ratingContainers.forEach(function(el) {
            const parentLi = el.closest('li');
            
            if (parentLi) {
                checkLinkBridge(parentLi);
            }

            if (el.querySelector('.rating-box')) return;

            const goodEl = el.querySelector('.good-rating-count');
            const okEl = el.querySelector('.ok-rating-count');
            const badEl = el.querySelector('.bad-rating-count');

            if (!goodEl && !okEl && !badEl) return;

            let feedbackUrl = "";
            
            if (parentLi) {
                const allLinks = parentLi.querySelectorAll('a');
                for (let i = 0; i < allLinks.length; i++) {
                    const a = allLinks[i];
                    const match = a.href.match(/\/scripts\/(\d+)/);
                    if (match) {
                        const baseUrl = a.href.split('/scripts/')[0];
                        feedbackUrl = baseUrl + '/scripts/' + match[1] + '/feedback';
                        break; 
                    }
                }
            }

            if (!feedbackUrl) {
                const urlMatch = window.location.pathname.match(/\/scripts\/(\d+)/);
                if (urlMatch) {
                    const langPart = window.location.origin + window.location.pathname.split('/scripts/')[0];
                    feedbackUrl = langPart + '/scripts/' + urlMatch[1] + '/feedback';
                }
            }

            const good = parseInt(goodEl ? goodEl.textContent : 0) || 0;
            const ok = parseInt(okEl ? okEl.textContent : 0) || 0;
            const bad = parseInt(badEl ? badEl.textContent : 0) || 0;
            const total = good + ok + bad;

            const avg = total > 0 ? ((good * 5) + (ok * 3) + (bad * 1)) / total : 0;
            const percent = (avg / 5) * 100;

            if (!feedbackUrl) feedbackUrl = "#";

            el.innerHTML = '';
            const link = document.createElement('a');
            link.href = feedbackUrl;
            link.className = 'rating-link';
            link.title = 'Feedback (Avg: ' + avg.toFixed(2) + ')';
            link.innerHTML = `
                <div class="rating-box">
                    <span class="rating-num">${avg.toFixed(2)}</span>
                    <div class="star-outer">
                        <div class="star-inner" style="width: ${percent}%"></div>
                    </div>
                    <span class="rating-total">(${total.toLocaleString()})</span>
                </div>
            `;
            el.appendChild(link);
        });
    };

    addStyles();
    transformRatings();

    const observer = new MutationObserver(transformRatings);
    observer.observe(document.body, { childList: true, subtree: true });
})();