Pixiv 推送识别器(增强修复版)

在 Pixiv 上标记作品为“新推送”或“已查看”,并统计被推荐次数,支持动态加载。

// ==UserScript==
// @name         Pixiv 推送识别器(增强修复版)
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  在 Pixiv 上标记作品为“新推送”或“已查看”,并统计被推荐次数,支持动态加载。
// @author       Gemini
// @match        https://www.pixiv.net/*
// @grant        none
// @run-at       document-idle
// @license       MIT
// ==/UserScript==

(function () {
    'use strict';

    const STORAGE_KEY = 'pixiv_push_history';
    const PROCESSED_MARKER = 'pixiv-push-identifier-processed';

    function loadHistory() {
        const data = localStorage.getItem(STORAGE_KEY);
        return data ? JSON.parse(data) : {};
    }

    function saveHistory(history) {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(history));
    }

    function extractArtworkId(url) {
        if (!url) return null;
        const match = url.match(/artworks\/(\d+)/);
        return match ? match[1] : null;
    }

    function markArtwork(element, count) {
        const tag = document.createElement('span');
        const isNew = count === 1;

        tag.style.cssText = `
            position: absolute !important;
            top: 6px !important;
            left: 6px !important;
            background: ${isNew ? '#1e90ff' : '#0055aa'} !important;
            color: white !important;
            padding: 4px 10px !important;
            font-size: 13px !important;
            font-weight: 700 !important;
            border-radius: 6px !important;
            z-index: 9999 !important;
            pointer-events: none !important;
            box-shadow: 0 2px 6px rgba(0,0,0,0.6) !important;
            line-height: 1.2 !important;
            text-transform: none !important;
            font-family: "Arial", "Microsoft YaHei", sans-serif !important;
        `;

        tag.textContent = isNew ? '新推送' : `已推送 ×${count}`;
        element.style.position = 'relative';
        element.appendChild(tag);
    }

    function processPage() {
        console.log("[Pixiv 推送识别器] 正在处理作品...");
        const history = loadHistory();
        let updated = false;

        const linkElements = document.querySelectorAll('a[href*="/artworks/"]');

        linkElements.forEach(linkElement => {
            const id = extractArtworkId(linkElement.getAttribute('href'));
            if (!id) return;

            const container = linkElement.closest('li') || linkElement.closest('div');
            if (!container || container.classList.contains(PROCESSED_MARKER)) return;
            container.classList.add(PROCESSED_MARKER);

            let title = '';
            const img = container.querySelector('img[alt]');
            if (img) {
                title = img.getAttribute('alt');
            } else {
                const titleEl = container.querySelector('h2');
                if (titleEl) title = titleEl.textContent;
            }

            if (history[id]) {
                history[id].count += 1;
                history[id].lastSeen = new Date().toISOString();
            } else {
                history[id] = {
                    title: title,
                    count: 1,
                    firstSeen: new Date().toISOString()
                };
            }

            markArtwork(container, history[id].count);
            updated = true;
        });

        if (updated) {
            saveHistory(history);
        }
    }

    const observer = new MutationObserver(mutationsList => {
        let found = false;
        for (const mutation of mutationsList) {
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                found = true;
                break;
            }
        }
        if (found) {
            clearTimeout(observer._debounce);
            observer._debounce = setTimeout(processPage, 500);
        }
    });

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

    setTimeout(processPage, 1000);
})();