[NOT WORKING] Pixiv User Scanner (Queue + Highlight + Follow/Unfollow Detect)

Scan Pixiv users lazily, highlight follow status, detect follow & unfollow actions

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         [NOT WORKING] Pixiv User Scanner (Queue + Highlight + Follow/Unfollow Detect)
// @namespace    Violentmonkey Scripts
// @version      1.5
// @description  Scan Pixiv users lazily, highlight follow status, detect follow & unfollow actions
// @author       Oppai1442
// @match        https://www.pixiv.net/en/artworks/*
// @grant        none
// @license      CC BY-NC-ND 4.0
// ==/UserScript==

(function () {
    'use strict';

    const scanned = new Set();
    const processed = new Map(); // userId → isFollowed
    const queue = [];

    const delay = ms => new Promise(res => setTimeout(res, ms));

    const getUserIdFromHref = href => {
        const match = href.match(/\/users\/(\d+)/);
        return match ? match[1] : null;
    };

    const highlightFollowed = (userId, isFollowed) => {
        const matches = document.querySelectorAll(`[data-gtm-value="${userId}"]`);
        matches.forEach(el => {
            const wrapper = el.closest('.sc-5a760b36-1.dUCFCr');
            if (!wrapper) return;

            const applied = wrapper.dataset.followMarked === 'true';

            if (isFollowed && !applied) {
                wrapper.style.border = '2px solid limegreen';
                wrapper.style.borderRadius = '6px';
                wrapper.style.padding = '2px';
                wrapper.dataset.followMarked = 'true';
            } else if (!isFollowed && applied) {
                wrapper.style.border = '';
                wrapper.style.borderRadius = '';
                wrapper.style.padding = '';
                wrapper.dataset.followMarked = 'false';
            }
        });
    };






    const fetchUserInfo = async (userId) => {
        if (processed.has(userId)) {
            highlightFollowed(userId, processed.get(userId));
            return;
        }

        const url = `https://www.pixiv.net/ajax/user/${userId}?full=1&lang=en`;
        try {
            const res = await fetch(url, { credentials: 'include' });
            const json = await res.json();
            if (!json.error) {
                const name = json.body.name;
                const isFollowed = json.body.isFollowed;
                processed.set(userId, isFollowed);
                highlightFollowed(userId, isFollowed);
                // console.log(`[${name}] (${userId}) Followed: ${isFollowed}`);
            }
        } catch (err) {
            console.error(`Error fetching user ${userId}:`, err);
        }
    };

    const processQueue = async () => {
        while (true) {
            if (queue.length > 0) {
                const userId = queue.shift();
                await fetchUserInfo(userId);
            }
            await delay(300 + Math.random() * 200);
        }
    };

    const scanPage = () => {
        const seenThisScan = new Set();

        document.querySelectorAll('a[href*="/users/"]').forEach(link => {
            const userId = getUserIdFromHref(link.getAttribute('href'));
            if (!userId || seenThisScan.has(userId)) return;
            seenThisScan.add(userId);

            // Nếu user chưa được quét, đưa vào queue
            if (!scanned.has(userId)) {
                scanned.add(userId);
                queue.push(userId);
            }

            // Gọi highlight nếu đã có data
            if (processed.has(userId)) {
                highlightFollowed(userId, processed.get(userId));
            }
        });
    };



    const observeFollowButtons = () => {
        document.body.addEventListener('click', (e) => {
            const btn = e.target.closest('[data-gtm-user-id]');
            if (btn) {
                const userId = btn.getAttribute('data-gtm-user-id');
                if (!userId) return;

                // Toggle trạng thái hiện tại
                const current = processed.get(userId) === true;
                const newState = !current;
                processed.set(userId, newState);
                highlightFollowed(userId, newState);
                // console.log(`[Follow toggled manually] ${userId} → ${newState}`);

                // (Optional) sync lại sau vài giây với server
                setTimeout(async () => {
                    try {
                        const res = await fetch(`https://www.pixiv.net/ajax/user/${userId}?full=1&lang=en`, { credentials: 'include' });
                        const json = await res.json();
                        if (!json.error) {
                            const actual = json.body.isFollowed;
                            processed.set(userId, actual);
                            highlightFollowed(userId, actual);
                            // console.log(`[Synced follow status] ${userId} → ${actual}`);
                        }
                    } catch (e) {
                        console.warn(`Sync failed for ${userId}`);
                    }
                }, 3000); // đợi cho chắc
            }
        }, true);
    };



    let scanTimeout = null;
    const observer = new MutationObserver(() => {
        if (scanTimeout) return;
        scanTimeout = setTimeout(() => {
            scanPage();
            scanTimeout = null;
        }, 100); // debounce
    });
    observer.observe(document.body, { childList: true, subtree: true });


    scanPage();
    processQueue();
    observeFollowButtons();
})();