Pixiv Block Users

Block users on Pixiv.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

Advertisement:

// ==UserScript==
// @name         Pixiv Block Users
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Block users on Pixiv.
// @author       Dr.Dree
// @match        https://www.pixiv.net/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=pixiv.net
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT

// ==/UserScript==



(function() {
    'use strict';

    const STORAGE_KEY = 'pixiv_blocked_ai_users';

    let isManageBtnVisible = GM_getValue('pb_btn_visible', true);

    GM_registerMenuCommand("Manage Button (On/Off)", () => {
        isManageBtnVisible = !isManageBtnVisible;
        GM_setValue('pb_btn_visible', isManageBtnVisible);

        const btn = document.getElementById('pb-manage-btn');
        if (btn) {
            btn.style.display = isManageBtnVisible ? 'flex' : 'none';
        }
    });

// 0. EXACT URL FILTER (Avoid tabs like /following, /illustrations...)
    function getCleanUserId(href) {
        try {
            const url = new URL(href, window.location.origin);
            // Only get URLs ending exactly with a numeric ID (e.g., /en/users/12345), ignoring trailing tabs
            const match = url.pathname.match(/^\/(?:[a-zA-Z-]+\/)?users\/(\d+)$/);
            return match ? match[1] : null;
        } catch (e) {
            return null;
        }
    }

    // 1. DATA MANAGEMENT
    function getBlockedUsers() {
        const savedData = localStorage.getItem(STORAGE_KEY);
        if (!savedData) return [];

        let parsed = JSON.parse(savedData);
        if (parsed.length > 0 && typeof parsed[0] === 'string') {
            parsed = parsed.map(id => ({ id: id, name: 'Unknown User' }));
            saveBlockedUsers(parsed);
        }
        return parsed;
    }

    function saveBlockedUsers(usersArray) {
        localStorage.setItem(STORAGE_KEY, JSON.stringify(usersArray));
    }

    // 2. MUTE UI INJECTOR
    function applyMutedUI(card) {
        // A. Replace image with Pixiv's native Mute UI
        const artworkLinks = Array.from(card.querySelectorAll('a')).filter(a => /\/artworks\/\d+/.test(a.pathname));
        if (artworkLinks.length > 0) {
            const thumbLink = artworkLinks[0];
            thumbLink.innerHTML = `
                <div style="width: 100%; height: 100%; aspect-ratio: 1 / 1; background-color: rgba(128, 128, 128, 0.1); display: flex; align-items: center; justify-content: center; border-radius: 4px; overflow: hidden;">
                    <svg viewBox="0 0 24 24" style="width: 48px; height: 48px; fill: currentColor; opacity: 0.3;">
                        <path d="M5.26763775,4 L9.38623853,11.4134814 L5,14.3684211 L5,18 L13.0454155,18 L14.1565266,20 L5,20 C3.8954305,20 3,19.1045695 3,18 L3,6 C3,4.8954305 3.8954305,4 5,4 L5.26763775,4 Z M9.84347336,4 L19,4 C20.1045695,4 21,4.8954305 21,6 L21,18 C21,19.1045695 20.1045695,20 19,20 L18.7323623,20 L17.6212511,18 L19,18 L19,13 L16,15 L15.9278695,14.951913 L9.84347336,4 Z M16,7 C14.8954305,7 14,7.8954305 14,9 C14,10.1045695 14.8954305,11 16,11 C17.1045695,11 18,10.1045695 18,9 C18,7.8954305 17.1045695,7 16,7 Z M7.38851434,1.64019979 L18.3598002,21.3885143 L16.6114857,22.3598002 L5.64019979,2.61148566 L7.38851434,1.64019979 Z"></path>
                    </svg>
                </div>
            `;
            thumbLink.onclick = (e) => e.preventDefault();
            thumbLink.style.cursor = 'default';

            // B. Change title text to "Muted"
            if (artworkLinks.length > 1) {
                const titleLink = artworkLinks[1];
                titleLink.textContent = 'Muted';
                titleLink.style.opacity = '0.5';
                titleLink.onclick = (e) => e.preventDefault();
                titleLink.style.cursor = 'default';
            }
        }

        // C. Safely hide avatar and author name (no more while-loop DOM climbing)
        const userLinksInCard = card.querySelectorAll('a[href*="/users/"]');
        userLinksInCard.forEach(link => {
            if (getCleanUserId(link.href)) {
                // Hide the adjacent block button (if any)
                if (link.nextElementSibling && link.nextElementSibling.title === 'Add this author to the blocklist') {
                    link.nextElementSibling.style.display = 'none';
                }
                // Hide name/avatar
                link.style.display = 'none';
            }
        });

        // D. Hide Bookmark button (Heart icon)
        const buttons = card.querySelectorAll('button');
        buttons.forEach(btn => btn.style.display = 'none');
    }

    // 3. CORE LOGIC: SMART FINDER
    function muteBlockedArtworks() {
        const blockedUsers = getBlockedUsers();
        if (blockedUsers.length === 0) return;

        const blockedIds = blockedUsers.map(user => user.id);
        const userLinks = document.querySelectorAll('a[href*="/users/"]');

        userLinks.forEach(link => {
            const userId = getCleanUserId(link.href);
            // Skip if not a valid user link or user is not blocked
            if (!userId || !blockedIds.includes(userId)) return;

            let card = link.closest('li');

            if (!card) {
                let parent = link.parentElement;
                for (let i = 0; i < 7; i++) {
                    if (!parent) break;
                    // Must contain an <a> tag leading to a SPECIFIC ARTWORK (/artworks/id) and contain an image
                    const hasArtThumbnail = Array.from(parent.querySelectorAll('a')).some(a =>
                        /\/artworks\/\d+$/.test(a.pathname) && (a.querySelector('img') || a.querySelector('[role="img"]'))
                    );

                    if (hasArtThumbnail) {
                        card = parent;
                        break;
                    }
                    parent = parent.parentElement;
                }
            }

            // SAFEGUARD FOR PROFILE PAGES
            // Ensure not to hide large blocks (Body, Main) and strictly DO NOT HIDE tags containing Headings (h1, h2)
            if (card && card.tagName !== 'BODY' && card.tagName !== 'MAIN') {
                const linkCount = card.querySelectorAll('a').length;
                const hasHeader = card.querySelector('h1, h2'); // The name on the profile header is usually an H1 or H2 tag

                if (linkCount < 20 && !hasHeader && !card.classList.contains('pb-muted-done')) {
                    card.classList.add('pb-muted-done');
                    applyMutedUI(card);
                }
            }
        });
    }

    // 4. INJECT BLOCK BUTTONS [🚫]
    function addBlockButtons() {
        const userLinks = document.querySelectorAll('a[href*="/users/"]:not(.has-block-btn)');

        userLinks.forEach(link => {
            link.classList.add('has-block-btn');

            // 1. Check for clean ID (Filter out tab links, info links)
            const userId = getCleanUserId(link.href);
            if (!userId) return;

            // 2. Skip if the link is inside a navigation bar (<nav>) (Filter out Home, Illustrations tabs)
            if (link.closest('nav')) return;

            // 3. Skip if it is an Avatar image
            if (link.querySelector('img') || link.querySelector('[role="img"]') || link.querySelector('svg')) {
                return;
            }

            const userName = link.textContent.trim();
            // 4. Skip if text is empty or accidentally matches system keywords
            if (!userName || /^(Home|Illustrations|Following|Followers)$/i.test(userName)) return;

            const btn = document.createElement('span');
            btn.innerHTML = ' 🚫';
            btn.title = 'Add this author to the blocklist';
            btn.style.cursor = 'pointer';
            btn.style.fontSize = '12px';
            btn.style.marginLeft = '4px';
            btn.style.opacity = '0.7';

            btn.onmouseover = () => btn.style.opacity = '1';
            btn.onmouseout = () => btn.style.opacity = '0.7';

            btn.onclick = function(e) {
                e.preventDefault();
                e.stopPropagation();

                let currentList = getBlockedUsers();

                if (!currentList.some(user => user.id === userId)) {
                    currentList.push({ id: userId, name: userName });
                    saveBlockedUsers(currentList);

                    console.log(`[Pixiv Blocker] Muted: ${userName} (ID: ${userId})`);
                    muteBlockedArtworks();
                }
            };

            link.parentNode.insertBefore(btn, link.nextSibling);
        });
    }

    // 5. VISUAL MANAGER UI
    function injectUI() {
        if (document.getElementById('pb-manage-btn')) return;

        const style = document.createElement('style');
        style.innerHTML = `
            #pb-manage-btn {
                position: fixed; bottom: 20px; left: 20px;
                background: rgba(128, 128, 128, 0.8);
                color: white; border: none;
                width: 45px; height: 45px; border-radius: 50%;
                cursor: pointer; z-index: 9998; font-size: 20px;
                display: flex; align-items: center; justify-content: center;
                box-shadow: 0 2px 5px rgba(0,0,0,0.3); transition: 0.3s;
            }
            #pb-manage-btn:hover { background: rgba(100, 100, 100, 1); }

            #pb-modal-overlay { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.6); z-index: 9999; display: none; align-items: center; justify-content: center; font-family: sans-serif; }
            #pb-modal-content { background: #fff; color: #333; padding: 20px; border-radius: 8px; width: 450px; max-height: 80vh; display: flex; flex-direction: column; box-shadow: 0 4px 15px rgba(0,0,0,0.3); }
            #pb-modal-header { font-size: 18px; font-weight: bold; margin-bottom: 15px; border-bottom: 1px solid #ddd; padding-bottom: 10px; display: flex; justify-content: space-between; }
            #pb-user-list { overflow-y: auto; flex-grow: 1; margin-bottom: 15px; padding-right: 5px; }
            .pb-user-item { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid #eee; }
            .pb-user-info { display: flex; flex-direction: column; }
            .pb-user-name { font-weight: bold; font-size: 14px; }
            .pb-user-id { font-size: 12px; color: #888; }

            .pb-unblock-btn { background: #ff4d4f; color: white; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; }
            .pb-unblock-btn:hover { background: #ff7875; }

            #pb-modal-footer { display: flex; justify-content: flex-end; gap: 10px; border-top: 1px solid #ddd; padding-top: 15px; }
            #pb-close-btn { background: #f0f0f0; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; color: #333; font-weight: bold; }
            #pb-close-btn:hover { background: #e0e0e0; }
            #pb-save-reload-btn { background: #0096fa; color: white; border: none; padding: 10px 15px; border-radius: 4px; cursor: pointer; font-weight: bold; }
            #pb-save-reload-btn:hover { background: #0077c8; }
        `;
        document.head.appendChild(style);

        const manageBtn = document.createElement('button');
        manageBtn.id = 'pb-manage-btn';
        manageBtn.innerHTML = '🛡️';
        manageBtn.title = 'Manage Blocklist';
        manageBtn.style.display = isManageBtnVisible ? 'flex' : 'none';
        document.body.appendChild(manageBtn);

        const modalOverlay = document.createElement('div');
        modalOverlay.id = 'pb-modal-overlay';

        const modalContent = document.createElement('div');
        modalContent.id = 'pb-modal-content';

        modalContent.innerHTML = `
            <div id="pb-modal-header">
                <span>Blocked Authors</span>
                <span id="pb-count" style="font-size: 14px; font-weight: normal; color: #666;">0 users</span>
            </div>
            <div id="pb-user-list"></div>
            <div id="pb-modal-footer">
                <button id="pb-close-btn">Close</button>
                <button id="pb-save-reload-btn">Save & Reload</button>
            </div>
        `;

        modalOverlay.appendChild(modalContent);
        document.body.appendChild(modalOverlay);

        const userListDiv = modalContent.querySelector('#pb-user-list');
        const countSpan = modalContent.querySelector('#pb-count');

        let tempBlockedUsers = [];

        function renderList() {
            countSpan.innerText = `${tempBlockedUsers.length} users`;
            userListDiv.innerHTML = '';

            if (tempBlockedUsers.length === 0) {
                userListDiv.innerHTML = '<div style="text-align: center; color: #999; margin-top: 20px;">No blocked authors yet.</div>';
                return;
            }

            tempBlockedUsers.forEach(user => {
                const item = document.createElement('div');
                item.className = 'pb-user-item';
                item.innerHTML = `
                    <div class="pb-user-info">
                        <span class="pb-user-name">${user.name}</span>
                        <span class="pb-user-id">ID: ${user.id}</span>
                    </div>
                    <button class="pb-unblock-btn" data-id="${user.id}">Unblock</button>
                `;
                userListDiv.appendChild(item);
            });

            modalContent.querySelectorAll('.pb-unblock-btn').forEach(btn => {
                btn.onclick = function() {
                    const idToRemove = this.getAttribute('data-id');
                    tempBlockedUsers = tempBlockedUsers.filter(u => u.id !== idToRemove);
                    renderList();
                };
            });
        }

        manageBtn.onclick = function() {
            tempBlockedUsers = getBlockedUsers();
            renderList();
            modalOverlay.style.display = 'flex';
        };

        modalContent.querySelector('#pb-close-btn').onclick = function() {
            modalOverlay.style.display = 'none';
        };

        modalContent.querySelector('#pb-save-reload-btn').onclick = function() {
            saveBlockedUsers(tempBlockedUsers);
            modalOverlay.style.display = 'none';
            window.location.reload();
        };
    }

    // 6. MAIN RUNNER
    function runApp() {
        addBlockButtons();
        muteBlockedArtworks();
        injectUI();
    }

    runApp();

    const observer = new MutationObserver(() => {
        runApp();
    });

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

})();