Greasy Fork

Greasy Fork is available in English.

Pixiv 显示书签数量和发布日期

支持用户主页、推荐和排行榜,支持识别列表类名以适应网站变动

当前为 2025-06-30 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name           Pixiv 显示书签数量和发布日期
// @name:en        Display bookmark counts and creation dates
// @namespace      http://tampermonkey.net/
// @version        2.0.1
// @description    支持用户主页、推荐和排行榜,支持识别列表类名以适应网站变动
// @description:en Supports user pages, recommendation lists, and ranking pages, supports identifying list class names to adapt to website changes
// @author         InMirrors
// @license        GPL-3.0-or-later
// @icon           data:image/png;base64,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
// @match          https://www.pixiv.net/*
// @grant          GM_addStyle
// @grant          GM_getValue
// @grant          GM_setValue
// @grant          GM_registerMenuCommand
// @run-at         document-idle
// ==/UserScript==

(function() {
    'use strict';

    // Cache for artwork data (bookmark count and create date)
    const artworkDataCache = {};

    // === Style Customization ===

    // 您可以在这里修改以下 CSS 变量来自定义书签数量元素的样式和位置。
    // 修改后请保存脚本并刷新 Pixiv 页面。

    // 位置 (使用百分比相对于图片容器定位)
    // 例如:
    // --bm-pos-bottom: 5%; --bm-pos-left: 5%; /* 左下角 */
    // --bm-pos-bottom: 5%; --bm-pos-right: 5%; /* 右下角 */
    // --bm-pos-top: 5%; --bm-pos-left: 5%; /* 左上角 */
    // --bm-pos-top: 5%; --bm-pos-right: 5%; /* 右上角 */
    // 如果某个方向不想设置,请使用 auto (例如:--bm-pos-right: auto;)
    const customStyles = `
    :root {
        /* 书签元素样式 */
        --bm-pos-bottom: 2px;    /* 位置:距离底部的距离 */
        --bm-pos-left: 2px;      /* 位置:距离左侧的距离 */
        --bm-pos-right: auto;   /* 位置:距离右侧的距离 */
        --bm-pos-top: auto;     /* 位置:距离顶部的距离 */

        --bm-bg-color: rgba(220, 220, 220, 0.5); /* 背景颜色 (rgba 包含透明度) */
        --bm-border-radius: 8px; /* 圆角弧度 */

        --bm-font-family: sans-serif; /* 文本字体 */
        --bm-font-size: 12px;    /* 文本字号 */
        --bm-font-weight: bold;  /* 文本字重 */
        --bm-text-color: #0069b1; /* 文本颜色 */
        --bm-text-opacity: 1.0;  /* 文本透明度 */

        --bm-padding: 0; /* 整个元素的内边距 (通常设为 0,内边距在链接上设置) */
        --bm-link-padding: 3px 6px 3px 18px; /* 链接的内边距 (用于控制文本与图标距离边框的距离) */

        --bm-icon-size: 10px; /* 心形图标大小 (宽度和高度) */
        --bm-icon-position: center left 6px; /* 心形图标位置 (垂直位置 靠左 距离左侧距离) */
        /* 注意:心形图标的颜色在 SVG 数据中指定 (#0069B1),如需修改请修改 SVG 数据 URL */
        /* 注意:心形图标的透明度由整个元素的背景透明度或文本透明度间接影响,
                或者通过修改 SVG 数据中的 fill="rgba(..., alpha)" 实现更精细控制 */

        /* 日期元素样式 */
        --cd-font-family : sans-serif; /* 文本字体 */
        --cd-font-size   : 12px;       /* 文本字号 */
        --cd-font-weight : normal;     /* 文本字重 */
        --cd-text-color  : #000000;  /* 文本颜色 */
        --cd-text-opacity: 0.7;        /* 文本透明度 */
        --cd-line-height : 20px;       /* 行高 */
    }
`;

    GM_addStyle(`
    ${customStyles}

    /* 书签数量元素本体 */
    .bmcount {
        position: absolute !important; /* 绝对定位 */
        z-index: 10; /* 确保在图片上方显示 */

        /* 使用自定义的位置变量 */
        bottom: var(--bm-pos-bottom, auto);
        left: var(--bm-pos-left, auto);
        right: var(--bm-pos-right, auto);
        top: var(--bm-pos-top, auto);

        /* 使用自定义的样式变量 */
        background-color: var(--bm-bg-color);
        border-radius: var(--bm-border-radius);
        padding: var(--bm-padding); /* 通常为 0 */

        /* 移除旧的布局样式 */
        text-align: initial !important; /* 取消居中 */
        padding-bottom: 0 !important; /* 移除底部填充 */
    }

    /* 书签数量链接和文本 */
    .bmcount a {
        display: block; /* 使 padding 生效 */
        height: initial !important;
        width: initial !important;
        border-radius: inherit !important; /* 继承父元素的圆角 */

        /* 使用自定义的文本和链接内边距变量 */
        padding: var(--bm-link-padding);

        /* 使用自定义的文本样式变量 */
        font-family: var(--bm-font-family);
        font-size: var(--bm-font-size) !important;
        font-weight: var(--bm-font-weight) !important;
        color: var(--bm-text-color) !important;
        opacity: var(--bm-text-opacity); /* 文本透明度 */
        text-decoration: none !important;

        /* 图标样式 */
        background-image: url("data:image/svg+xml;charset=utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2210%22 height=%2210%22 viewBox=%220 0 12 12%22><path fill=%22%230069B1%22 d=%22M9,1 C10.6568542,1 12,2.34314575 12,4 C12,6.70659075 10.1749287,9.18504759 6.52478604,11.4353705 L6.52478518,11.4353691 C6.20304221,11.6337245 5.79695454,11.6337245 5.4752116,11.4353691 C1.82507053,9.18504652 0,6.70659017 0,4 C1.1324993e-16,2.34314575 1.34314575,1 3,1 C4.12649824,1 5.33911281,1.85202454 6,2.91822994 C6.66088719,1.85202454 7.87350176,1 9,1 Z%22/></svg>") !important;
        background-position: var(--bm-icon-position) !important;
        background-size: var(--bm-icon-size) !important;
        background-repeat: no-repeat !important;
    }

    /* 移除针对特定类名的旧样式 */
    .JoCpVnw .bmcount {
        padding-bottom: initial !important;
    }

    /* 日期元素本体 */
    .createdate {
        /* 使用自定义的文本样式 */
        font-family: var(--cd-font-family);
        font-size: var(--cd-font-size) !important;
        font-weight: var(--cd-font-weight) !important;
        color: var(--cd-text-color) !important;
        opacity: var(--cd-text-opacity);
        line-height: var(--cd-line-height);
        text-decoration: none !important; /* 移除可能的下划线 */
        white-space: nowrap; /* 防止日期换行 */
    }
`);



    // === Storage and Selector Management ===

    const STORAGE_KEY = 'pixiv_bookmark_selectors';

    // Selectors that seem relatively stable or cover specific cases
    const ALWAYS_INCLUDED_SELECTORS = [
        '.ranking-item', // 排行榜
        '.gtm-illust-recommend-zone[data-gtm-recommend-zone="discovery"] li', // 插图页面下方的推荐
    ];

    let dynamicArtworkSelectors = loadSelectors();
    let currentCombinedSelector = buildSelectorString();

    // Load selectors from storage
    function loadSelectors() {
        const stored = GM_getValue(STORAGE_KEY, '[]');
        try {
            const selectors = JSON.parse(stored);
            if (Array.isArray(selectors) && selectors.every(s => typeof s === 'string')) {
                return selectors.filter(s => s.trim() !== '');
            }
            console.error("Failed to parse stored selectors, returning empty array.", stored);
            return [];
        } catch (e) {
            console.error("Error loading selectors from storage:", e);
            return [];
        }
    }

    // Save selectors to storage
    function saveSelectors(selectors) {
        GM_setValue(STORAGE_KEY, JSON.stringify(selectors));
        dynamicArtworkSelectors = selectors; // Update in-memory variable
        currentCombinedSelector = buildSelectorString(); // Rebuild selector string
        // Note: The MutationObserver will pick up the new currentCombinedSelector
        // on its next execution cycle after a DOM change.
    }

    // Build the full CSS selector string for querySelectorAll
    function buildSelectorString() {
        // Note: .ranking-item (appears in the ranking page) is an item selector, not a container selector
        const containerSelectors = dynamicArtworkSelectors.filter(s => !s.endsWith(' li') && s !== '.ranking-item'); // Exclude .ranking-item from containers
        const itemSelectors = dynamicArtworkSelectors.filter(s => s.endsWith(' li') || s === '.ranking-item'); // Include .ranking-item as an item

        const alwaysIncludedContainerSelectors = ALWAYS_INCLUDED_SELECTORS.filter(s => !s.endsWith(' li') && s !== '.ranking-item');
        const alwaysIncludedItemSelectors = ALWAYS_INCLUDED_SELECTORS.filter(s => s.endsWith(' li') || s === '.ranking-item');

        const finalContainerSelectors = [...new Set([...containerSelectors, ...alwaysIncludedContainerSelectors])];
        const finalItemSelectors = [...new Set([...itemSelectors, ...alwaysIncludedItemSelectors])];

        // Build the query: all items + li descendants of all containers
        const queryParts = [
            ...finalItemSelectors, // Items already selected (.ranking-item is here)
            ...finalContainerSelectors.map(s => s + ' li') // li inside containers
        ];

        // Add the :not([data-dummybmc]) exclusion to each part
        const finalQuery = queryParts.map(s => s + ':not([data-dummybmc])').join(',');

        console.log("Built selector string:", finalQuery);
        return finalQuery;
    }

    // Find potential new container selectors on the current page
    // 基本只有用户主页会用到这个功能,排行榜和推荐都是固定类名
    function findPotentialSelectors() {
        const allPotentialOnPage = new Set(); // 记录在页面上找到的所有潜在选择器
        const existingSelectors = new Set([...dynamicArtworkSelectors, ...ALWAYS_INCLUDED_SELECTORS]);
        const newFound = new Set(); // 记录在页面上找到且是新的选择器

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

        artworkLinks.forEach(link => {
            const li = link.closest('li');
            if (!li) return;

            const ul = li.parentElement;
            if (!ul || ul.tagName !== 'UL') return;

            const container = ul.parentElement;
            if (container && (container.tagName === 'DIV' || container.tagName === 'SECTION')) {
                if (container.classList.length > 0) {
                    const selector = '.' + Array.from(container.classList).join('.');
                    allPotentialOnPage.add(selector); // 添加到所有找到的集合

                    // 如果这个选择器不在现有列表中,则添加到新找到的集合
                    if (!existingSelectors.has(selector)) {
                        newFound.add(selector);
                    }
                }
            }
        });

        // 返回包含详细信息的对象
        return {
            totalFound: Array.from(allPotentialOnPage), // 页面上找到的所有潜在选择器
            newFound: Array.from(newFound)              // 页面上找到且是新的选择器
        };
    }



    // === Context Menu Commands ===

    GM_registerMenuCommand("添加 (Add) 当前页面的 Pixiv 书签选择器", async () => {
        const result = findPotentialSelectors();

        if (result.totalFound.length === 0) {
            // 情况 1: 页面上没有找到任何潜在的选择器
            alert("未能在当前页面找到任何潜在的书签列表容器结构。请确保当前页面显示有插图列表。");
        } else {
            // 页面上找到了潜在的选择器
            if (result.newFound.length === 0) {
                // 情况 2: 找到了,但都是已存在的
                alert("在当前页面找到了潜在的书签列表容器结构,但所有找到的选择器都已存在于列表中,无需添加新的。");
                console.log("Found existing potential selectors:", result.totalFound.join(', '));
            } else {
                // 情况 3: 找到了新的选择器
                const currentSelectors = loadSelectors(); // 再次加载最新状态
                const updatedSelectors = [...new Set([...currentSelectors, ...result.newFound])]; // 合并并去重

                // 理论上 newFound 不为空时,updatedSelectors 长度应该大于 currentSelectors 长度,但为了严谨还是检查一下
                if (updatedSelectors.length > currentSelectors.length) {
                    saveSelectors(updatedSelectors);
                    const addedList = result.newFound.join('\n');
                    alert(`成功添加了 ${result.newFound.length} 个新的书签列表容器选择器:\n\n${addedList}\n\n脚本将尝试使用这些新的选择器,请刷新页面使变更生效。`);
                    console.log("Added new selectors:", result.newFound.join(', '));
                } else {
                    // 这通常不应该发生,除非 findPotentialSelectors 或 saveSelectors 逻辑有误
                    alert("找到了新的选择器,但在保存时未能实际增加列表项。请检查控制台输出。");
                    console.error("Logic error: newFound is not empty, but list size did not increase.");
                    console.log("New found:", result.newFound);
                    console.log("Current selectors:", currentSelectors);
                    console.log("Updated selectors (after merge):", updatedSelectors);
                }
            }
        }
    });

    GM_registerMenuCommand("清除 (Clear) 已添加的 Pixiv 书签选择器", () => {
        if (confirm("确定要清除所有动态学习到的 Pixiv 书签列表容器选择器吗?这可能导致脚本失效,直到重新添加。")) {
            saveSelectors([]);
            alert("已清除所有动态书签选择器。");
        }
    });



    // === Utils ===

    /**
     * 基于 ISO 字符串快速生成 "yyyy-MM-dd HH:mm"
     * 因为 pixiv 的时间精度只到分,秒部分全是 0,所以本函数去掉秒和微秒部分
     * @param {Date} date - 要格式化的 Date 对象
     * @returns {string} 格式化后的字符串
     */
    function formatFromISO(date) {
        const iso = date.toISOString();              // e.g. "yyyy-MM-ddTHH:mm:ss.fffZ"
        const [datePart, timePart] = iso.split('T'); // ["yyyy-MM-dd", "HH:mm:ss.fffZ"]
        const time = timePart.slice(0, 5);           // "HH:mm"
        return `${datePart} ${time}`;                // "yyyy-MM-dd HH:mm:ss"
    }

    /**
     * 按目标时区偏移后再格式化
     * @param {Date} date - 原始 Date 对象(本地时区或任意时区)
     * @param {number} timeZoneCode - 目标时区,例如 +8。默认使用当前时区
     * @returns {string} 格式化后的目标时区时间字符串
     */
    function formatWithTimezone(date, timeZoneCode = -date.getTimezoneOffset() / 60) {
        // 其实 getTime() 返回的已经是当前时区的时间戳了,但之后的 toISOString() 会引入偏移
        const utcTimestamp = date.getTime();
        // 所以加上一个偏移以抵消 toISOString() 引入的偏移
        const targetTimestamp = utcTimestamp + timeZoneCode * 60 * 60000;
        const targetDate = new Date(targetTimestamp);
        return formatFromISO(targetDate);
    }



    // === Mutation Observer ===

    const doneCheckSelectors = '.bmcount , .bookmark-count , a[href*="/bookmark_detail.php?illust_id="]';

    // 辅助函数:根据ID获取作品数据 (书签数和创建日期)
    async function fetchArtworkData(id) {
        //console.log(`Attempting to fetch data for ID: ${id}`);
        // Check cache first
        if (artworkDataCache[id]) {
            //console.log(`Cache hit for ID: ${id}`);
            return artworkDataCache[id];
        }

        try {
            const response = await fetch("https://www.pixiv.net/ajax/illust/" + id, { credentials: "omit" });
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            const data = await response.json();
            const artworkData = {
                bookmarkCount: data?.body?.bookmarkCount,
                createDate: data?.body?.createDate // Extract createDate
            };

            // Store in cache
            artworkDataCache[id] = artworkData;
            //console.log(`Fetched and cached data for ID ${id}:`, artworkData);
            return artworkData;

        } catch (error) {
            console.error("Error fetching artwork data for ID", id, ":", error);
            // Store error state in cache to avoid repeated failed requests
            artworkDataCache[id] = { error: true };
            throw error; // Re-throw to be caught by the caller
        }
    } // fetchArtworkData

    // 辅助函数:插入书签数和创建日期元素
    function insertArtworkElements(listItem, id, artworkData) {
        // Check if elements already exist within this item
        // Use a new data attribute to mark as fully processed
        if (listItem.hasAttribute("data-processed-bmc-cd")) {
            //console.log(`Item ${id} already has elements inserted.`);
            return;
        }

        const insertionParentBM = listItem.querySelector('a[href*="/artworks/"]');
        const insertionParentCD = listItem.querySelector(':scope > div');

        if (!insertionParentBM) {
            console.warn(`Insertion parent of bmcount elememt not found for item ID ${id}. Skipping element insertion.`);
            return;
        }
        if (!insertionParentCD) {
            console.warn(`Insertion parent of createdate elememt not found for item ID ${id}. Skipping element insertion.`);
            return;
        }

        // Insert bookmark count if available and not already present
        if (artworkData.bookmarkCount !== undefined && artworkData.bookmarkCount !== null) {
            // Check specifically for the .bmcount element within this item
            if (!listItem.querySelector('.bmcount')) {
                // Insert bookmark count element
                insertionParentBM.insertAdjacentHTML("beforeend", '<div class="bmcount"><a href="/bookmark_detail.php?illust_id=' + id + '">' + artworkData.bookmarkCount + "</a></div>");
            }
        }

        // Insert create date if available and not already present, and if it's an LI element
        if (listItem.tagName === 'LI' && artworkData.createDate) {
            // Check specifically for the .createdate element within this item
            if (!listItem.querySelector('.createdate')) {
                try {
                    // Format date to yyyy-MM-dd HH:mm
                    const date = new Date(artworkData.createDate);
                    const formattedDate = formatWithTimezone(date);

                    // Insert create date element
                    // Insert after the div containing the title/artist link, which is a sibling of insertionParent
                    insertionParentCD.insertAdjacentHTML("afterend", '<div class="createdate">' + formattedDate + "</div>");

                } catch (e) {
                    console.error("Error formatting or inserting create date for ID", id, ":", e);
                }
            }
        }

        // Mark the listItem as fully processed
        listItem.dataset.processedBmcCd = 'true';
    } // insertArtworkElements

    // 处理元素,添加书签数和创建日期
    async function processSingleArtworkElement(Each) {
        // Each could be an LI or a SECTION.ranking-item
        const listItem = (Each.tagName === 'LI' || Each.tagName === 'SECTION') ? Each : Each.closest('li, section');

        // Check if it's a valid item and hasn't been fully processed
        if (!listItem || listItem.hasAttribute("data-processed-bmc-cd")) {
            return;
        }

        // Ranking items (SECTION) should only get bookmark count, not date
        if (listItem.tagName === 'SECTION') {
            // For SECTION, only check if bookmark count is already inserted
            if (listItem.querySelector('.bmcount')) {
                listItem.dataset.processedBmcCd = 'true'; // Mark SECTION as processed if it has bmcount
                return;
            }
        } else if (listItem.tagName !== 'LI') {
            // Only process LI and SECTION elements
            return;
        }


        let id = null;
        const artworkLink = listItem.querySelector('a[href*="/artworks/"]');

        // If artworkLink not found, cannot proceed
        if (!artworkLink) {
            return;
        }

        // Extract ID from the href attribute
        // Format: "/artworks/ID" or "/lang/artworks/ID"
        id = /\d+$/.exec(artworkLink.href)?.[0];

        // If ID not found, skip
        if (!id) {
            return;
        }

        // Check cache first
        let artworkData = artworkDataCache[id];

        if (artworkData) {
            // If data is in cache (even error state), attempt insertion
            if (!artworkData.error) {
                insertArtworkElements(listItem, id, artworkData);
            } else {
                // If cached data indicates error, mark item as processed to avoid retries
                listItem.dataset.processedBmcCd = 'error';
            }
        } else {
            // Data not in cache, fetch it
            // Use a temporary marker to prevent duplicate fetches for the same element
            if (listItem.hasAttribute("data-fetching-bmc-cd")) {
                return;
            }
            listItem.dataset.fetchingBmcCd = 'true';

            try {
                artworkData = await fetchArtworkData(id);
                insertArtworkElements(listItem, id, artworkData);
            } catch (error) {
                // Error already logged in fetchArtworkData
                listItem.dataset.processedBmcCd = 'error'; // Mark item as processed with error
            } finally {
                // Remove temporary fetching marker
                delete listItem.dataset.fetchingBmcCd;
            }
        }
    } // processSingleArtworkElement()

    // MutationObserver 回调函数调用处理函数
    const observer = new MutationObserver((mutations) => {
        // On any mutation, re-query all matching elements and process them.
        // processSingleArtworkElement handles checking if an element needs processing.
        document.querySelectorAll(currentCombinedSelector).forEach(processSingleArtworkElement);
    });

    // Start observing the body for changes
    observer.observe(document.body, { childList: true, subtree: true });

    console.log("Pixiv Bookmark Count script started.");
    console.log("Initial selectors:", dynamicArtworkSelectors);
    console.log("Always included selectors:", ALWAYS_INCLUDED_SELECTORS);
    console.log("Combined query selector:", currentCombinedSelector);

})();