Greasy Fork

Greasy Fork is available in English.

Pixiv 缩略图中显示书签数量

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name           Pixiv 缩略图中显示书签数量
// @name:en        Display bookmark counts in Pixiv thumbnails
// @namespace      http://tampermonkey.net/
// @version        1.8
// @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';

// === 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)" 实现更精细控制 */
    }
`;

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;
    }
`);



// === 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("已清除所有动态书签选择器。");
    }
});



// === Mutation Observer ===

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

    // 辅助函数:根据ID获取书签数并插入到元素中
    async function fetchAndInsertBookmarkCount(listItem, id, insertionParent) {
        //console.log(`Attempting to fetch for ID: ${id}`);
        // 再次检查,防止在等待过程中或观察器触发后元素被处理
        if (listItem.querySelectorAll(doneCheckSelectors).length > 0 || listItem.hasAttribute("data-bmcount")) {
            //console.log(`Item ${id} already processed when fetchAndInsertBookmarkCount was called.`);
            if (listItem.hasAttribute("data-dummybmc")) {
                delete listItem.dataset.dummybmc; // 清理临时标记
            }
            listItem.dataset.bmcount = listItem.dataset.bmcount || 'exists'; // Set bmcount if not already set by a successful run
            return;
        }

        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 bmcount = data?.body?.bookmarkCount;

            if (bmcount !== undefined && bmcount !== null) {
                // 将元素插入到图片容器内
                if (listItem.querySelectorAll(doneCheckSelectors).length === 0) { // Final check before insert
                    insertionParent.insertAdjacentHTML("beforeend", '<div class="bmcount"><a href="/bookmark_detail.php?illust_id=' + id + '">' + bmcount + "</a></div>");
                }
                listItem.dataset.bmcount = bmcount; // 标记处理成功并保存书签数
            } else {
                listItem.dataset.bmcount = '0'; // 标记处理成功但书签数为 0
            }

        } catch (error) {
            console.error("Error fetching bookmark count for artwork ID", id, ":", error);
            listItem.dataset.bmcount = 'error'; // 标记处理失败
        } finally {
            // 无论成功或失败,处理流程结束,移除临时标记
            if (listItem.hasAttribute("data-dummybmc")) {
                delete listItem.dataset.dummybmc;
            }
        }
    } // fetchAndInsertBookmarkCount


    // 处理元素,添加书签数
    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 processed or is currently being processed
        if (!listItem || listItem.hasAttribute("data-dummybmc") || listItem.hasAttribute("data-bmcount")) {
            return;
        }

        // Mark as being processed temporarily
        listItem.dataset.dummybmc = "";

        // Check if bookmark count element already exists within this item
        if (listItem.querySelectorAll(doneCheckSelectors).length > 0) {
            delete listItem.dataset.dummybmc; // Clean up dummy mark
            listItem.dataset.bmcount = 'exists'; // Mark as already has the element
            return;
        }

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

        // If artworkLink not found, cannot proceed
        if (!artworkLink) {
            delete listItem.dataset.dummybmc; // Clean up dummy mark
            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) {
            delete listItem.dataset.dummybmc; // Clean up dummy mark
            return;
        }

        fetchAndInsertBookmarkCount(listItem, id, artworkLink);
   } // 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);

})();