Greasy Fork

Anilist Rating Tier Indicator

Adds a tier badge next to ratings on Anilist, including Mean Score. Supports different scoring systems.

目前为 2025-02-12 提交的版本。查看 最新版本

// ==UserScript==
// @name         Anilist Rating Tier Indicator
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Adds a tier badge next to ratings on Anilist, including Mean Score. Supports different scoring systems.
// @author       hiddenhokage, Claude, ChatGPT
// @match        *://anilist.co/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    // Updated tier definitions with colors
    const tiers = [
        { min: 95, max: 100, label: 'S+', color: '#FFD700', textColor: '#000000' }, // S+ Tier, Gold
        { min: 85, max: 94.9, label: 'S', color: '#ff7f00', textColor: '#FFFFFF' }, // S Tier, Orange
        { min: 75, max: 84.9, label: 'A', color: '#aa00ff', textColor: '#FFFFFF' }, // A Tier, Purple
        { min: 65, max: 74.9, label: 'B', color: '#007fff', textColor: '#FFFFFF' }, // B Tier, Blue
        { min: 55, max: 64.9, label: 'C', color: '#00aa00', textColor: '#FFFFFF' }, // C Tier, Green
        { min: 41, max: 54.9, label: 'D', color: '#aaaaaa', textColor: '#FFFFFF' }, // D Tier, Gray
        { min: 0, max: 40.9, label: 'F', color: '#666666', textColor: '#FFFFFF' } // F Tier, Dark Gray
    ];

    function getTier(rating) {
        return tiers.find(tier => rating >= tier.min && rating <= tier.max) || null;
    }

    function createBadge(tier, isBlockView = false) {
        let badge = document.createElement('span');
        badge.textContent = tier.label;

        badge.style.cssText = `
            background-color: ${tier.color};
            color: ${tier.textColor};
            font-size: ${isBlockView ? '10px' : '12px'};
            font-weight: bold;
            padding: ${isBlockView ? '1px 4px' : '2px 6px'};
            border-radius: 4px;
            display: inline-block;
            margin-left: 5px;
            vertical-align: middle;
            white-space: nowrap;
        `;
        return badge;
    }

    function getScoreSystem() {
        const container = document.querySelector('.content.container');
        if (container) {
            if (container.querySelector('.medialist.table.POINT_100')) return 'POINT_100';
            if (container.querySelector('.medialist.table.POINT_10_DECIMAL')) return 'POINT_10';
            if (container.querySelector('.medialist.table.POINT_5')) return 'POINT_5';
        }
        return 'UNKNOWN';
    }

    function normalizeScore(score, scoreSystem, isPercentage = false) {
        if (isPercentage) return score; // Percentage scores are already in the right format

        const numericScore = parseFloat(score);
        if (isNaN(numericScore)) return null;

        switch (scoreSystem) {
            case 'POINT_100':
                return numericScore; // Already in 100-point scale
            case 'POINT_10':
                return numericScore * 10; // Convert 10-point to 100-point
            case 'POINT_5':
                return numericScore * 20; // Convert 5-point to 100-point (5 → 100, 4 → 80, 3 → 60, 2 → 40, 1 → 20)
            default:
                return numericScore * 10; // Default to 10-point conversion
        }
    }

    function processScoreElement(el, isPercentage = false, isBlockView = false) {
        if (el.dataset.tierModified) return;
        el.dataset.tierModified = "true";

        const scoreSystem = getScoreSystem();
        let ratingText = el.getAttribute('score') || el.innerText.trim().replace('%', '');
        let normalizedRating = normalizeScore(ratingText, scoreSystem, isPercentage);

        if (normalizedRating === null) return;

        let tier = getTier(normalizedRating);
        if (tier) {
            const container = document.createElement('div');
            container.style.cssText = `
                display: inline-flex;
                align-items: center;
                gap: 4px;
                ${isBlockView ? 'background-color: rgba(0, 0, 0, 0.5); padding: 2px 6px; border-radius: 4px; overflow: hidden;' : ''}
            `;

            const scoreEl = document.createElement('span');
            scoreEl.textContent = isPercentage ? `${ratingText}%` : ratingText;

            container.appendChild(scoreEl);
            container.appendChild(createBadge(tier, isBlockView));

            el.textContent = '';
            el.appendChild(container);
        }
    }

    function addTierIndicators() {
        // List View (Decimal Scores)
        document.querySelectorAll('.score:not(.media-card .score)').forEach(el => {
            processScoreElement(el, false, false);
        });

        // Block View (Media Cards)
        document.querySelectorAll('.entry-card .score').forEach(el => {
            processScoreElement(el, false, true);
        });

        // Process both Average Score and Mean Score
        document.querySelectorAll('.data-set').forEach(dataSet => {
            const label = dataSet.querySelector('.type');
            const value = dataSet.querySelector('.value');

            if (label && value && !value.dataset.tierModified &&
                (label.innerText.includes('Average Score') || label.innerText.includes('Mean Score'))) {
                processScoreElement(value, true, false);
            }
        });
    }

    function initializeScript() {
        addTierIndicators();

        const statsObserver = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList' || mutation.type === 'subtree') {
                    addTierIndicators();
                }
            });
        });

        statsObserver.observe(document.body, {
            childList: true,
            subtree: true,
            characterData: true
        });
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeScript);
    } else {
        initializeScript();
    }

    window.addEventListener('popstate', () => {
        setTimeout(addTierIndicators, 100);
    });

    const pushState = history.pushState;
    history.pushState = function () {
        pushState.apply(history, arguments);
        setTimeout(addTierIndicators, 100);
    };
})();