Greasy Fork is available in English.
将视觉上的绿/黄/紅 5 级进度条转换为 0..5 星级评分。对于 SleazyFork,它还将损坏的链接重定向到 GreasyFork。
当前为
// ==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 });
})();