Kemono 更新標示 (彩色優化版)

為「今日/昨日/前日」的更新附加紅/黃/綠色的文字標示,方便快速識別。只處理新載入的內容,避免重複掃描,解決 CPU 佔用過高的問題。

// ==UserScript==
// @name              Kemono 更新標示 (彩色優化版)
// @name:zh-TW        Kemono 更新標示 (彩色優化版)
// @name:zh-CN        Kemono 更新标记 (彩色优化版)
// @namespace         https://greasyfork.org/users/YOUR-USER-ID  // <-- 請將 YOUR-USER-ID 換成你的 Greasy Fork 用戶 ID
// @version           2.0.0
// @description       Highlights recent posts on Kemono with colored labels: "Today" (Red), "Yesterday" (Yellow), and "Day Before" (Green). High-performance and optimized to only process new content, fixing high CPU usage issues.
// @description:zh-TW 為「今日/昨日/前日」的更新附加紅/黃/綠色的文字標示,方便快速識別。只處理新載入的內容,避免重複掃描,解決 CPU 佔用過高的問題。
// @description:zh-CN 为“今日/昨日/前日”的更新附加红/黄/绿色的文字标记,方便快速识别。只处理新加载的内容,避免重复扫描,解决 CPU 占用过高的问​​题。
// @author            Your Name Here // <-- 請改成你的名字或暱稱
// @match             https://kemono.cr/*
// @match             http://kemono.cr/*
// @match             https://kemono.su/*
// @match             http://kemono.su/*
// @grant             none
// @run-at            document-idle
// @license           MIT
// ==/UserScript==

(function() {
    'use strict';

    const prefix = '[KemonoCR]';
    const log = (...args) => console.log(prefix, ...args);

    // --- 日期與樣式設定 ---
    const toYMD = (date) => {
        const y = date.getFullYear();
        const m = String(date.getMonth() + 1).padStart(2, '0');
        const d = String(date.getDate()).padStart(2, '0');
        return `${y}-${m}-${d}`;
    };

    const today = new Date();
    const yesterday = new Date();
    yesterday.setDate(today.getDate() - 1);
    const dayBeforeYesterday = new Date();
    dayBeforeYesterday.setDate(today.getDate() - 2);

    // 將日期、文字和顏色映射在一起
    const dateMap = {
        [toYMD(today)]: { text: ' 今日更新', color: 'red' },
        [toYMD(yesterday)]: { text: ' 昨天更新', color: 'gold' }, // 使用 gold,比 yellow 在白色背景上更易讀
        [toYMD(dayBeforeYesterday)]: { text: ' 前天更新', color: 'green' },
    };

    log('Date map with colors initialized:', dateMap);

    /**
     * 處理單一 <time> 元素
     * @param {HTMLElement} el
     */
    function processElement(el) {
        // 如果已處理過,直接跳過,這是性能優化的關鍵
        if (el.dataset.kemonoProcessed) {
            return;
        }

        // 只取日期部分進行比對
        const dateText = (el.textContent || '').trim().split(' ')[0];
        const dateInfo = dateMap[dateText];

        if (dateInfo) {
            // 創建一個新的 span 元素來包裹帶有顏色的文字
            const labelSpan = document.createElement('span');
            labelSpan.textContent = dateInfo.text;
            labelSpan.style.color = dateInfo.color;
            labelSpan.style.fontWeight = 'bold'; // 加粗讓文字更顯眼

            // 將這個 span 附加到 <time> 元素的末尾
            el.appendChild(labelSpan);

            log('  -> Matched & Appended:', dateText, dateInfo.text, `(Color: ${dateInfo.color})`);
        }

        // 標記為已處理,避免重複執行
        el.dataset.kemonoProcessed = 'true';
    }

    /**
     * 在指定的節點內尋找並處理所有未處理的 <time.timestamp> 元素
     * @param {Node} targetNode
     */
    function findAndProcessInNode(targetNode) {
        if (targetNode.nodeType !== Node.ELEMENT_NODE) {
            return;
        }
        // 使用 :not 選擇器,高效地跳過已處理的元素
        const elements = targetNode.querySelectorAll('time.timestamp:not([data-kemono-processed])');
        if (elements.length > 0) {
            log(`Found ${elements.length} new element(s) to process.`);
            elements.forEach(processElement);
        }
    }

    // --- 腳本主邏輯 ---

    // 1. 頁面初次載入時,處理所有現存的元素
    log('Initial page scan...');
    findAndProcessInNode(document.body);
    log('Initial scan complete.');

    // 2. 設定 MutationObserver 來監控後續動態加入的內容
    const observer = new MutationObserver((mutationsList) => {
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                // 只對新增加的節點進行掃描,效率極高
                mutation.addedNodes.forEach(node => {
                    findAndProcessInNode(node);
                });
            }
        }
    });

    // 開始監聽 document.body 的子節點變化
    observer.observe(document.body, {
        childList: true, // 只關心子節點的增加或刪除
        subtree: true    // 監聽所有後代節點
    });

})();