Greasy Fork

Anilist Rating Tier Indicator

Adds a tier badge next to ratings on Anilist, including Mean Score.

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

// ==UserScript==
// @name         Anilist Rating Tier Indicator
// @namespace    http://tampermonkey.net/
// @version      1.9
// @description  Adds a tier badge next to ratings on Anilist, including Mean Score.
// @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');

        // Use shortened labels for block view
        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 processScoreElement(el, isPercentage = false, isBlockView = false) {
        if (el.dataset.tierModified) return;
        el.dataset.tierModified = "true";

        // Get score from the score attribute if available, otherwise from text content
        let ratingText = el.getAttribute('score') || el.innerText.trim().replace('%', '');
        let rating = parseFloat(ratingText);
        if (isNaN(rating)) return;

        let tier = getTier(isPercentage ? rating : rating * 10);
        if (tier) {
            // Create container for score and badge
            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;' : ''}
            `;

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

            // Add elements to container
            container.appendChild(scoreEl);
            container.appendChild(createBadge(tier, isBlockView));

            // Replace original content
            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);
            }
        });
    }

    // Initial load
    function initializeScript() {
        addTierIndicators();

        // Set up a more specific observer for the statistics section
        const statsObserver = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList' || mutation.type === 'subtree') {
                    addTierIndicators();
                }
            });
        });

        // Observe the entire document for dynamic content loading
        statsObserver.observe(document.body, {
            childList: true,
            subtree: true,
            characterData: true
        });
    }

    // Handle initial page load
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initializeScript);
    } else {
        initializeScript();
    }

    // Handle navigation events in the SPA
    window.addEventListener('popstate', () => {
        setTimeout(addTierIndicators, 100);
    });

    // Handle push state changes (when using the site's navigation)
    const pushState = history.pushState;
    history.pushState = function () {
        pushState.apply(history, arguments);
        setTimeout(addTierIndicators, 100);
    };
})();