Comick Comic Type Column

Adds a column showing comic type (Manga/Manhwa/Manhua).

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

// ==UserScript==
// @name         Comick Comic Type Column
// @namespace    https://github.com/GooglyBlox
// @version      1.0
// @description  Adds a column showing comic type (Manga/Manhwa/Manhua).
// @author       GooglyBlox
// @match        https://comick.io/user/*/list*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    let comicCountryMap = new Map();
    let isProcessing = false;

    function extractUserIdFromUrl() {
        const urlParts = window.location.pathname.split('/');
        const userIndex = urlParts.indexOf('user');
        return userIndex !== -1 && userIndex + 1 < urlParts.length ? urlParts[userIndex + 1] : null;
    }

    async function fetchUserFollows(userId) {
        try {
            const response = await fetch(`https://api.comick.io/user/${userId}/follows`);
            if (!response.ok) throw new Error(`HTTP ${response.status}`);
            return await response.json();
        } catch (error) {
            console.error('Failed to fetch user follows:', error);
            return [];
        }
    }

    function getComicTypeFromCountry(country) {
        switch (country) {
            case 'kr':
            case 'gb':
                return 'Manhwa';
            case 'cn':
                return 'Manhua';
            case 'jp':
                return 'Manga';
            default:
                return 'Unknown';
        }
    }

    function buildComicCountryMap(followsData) {
        comicCountryMap.clear();

        followsData.forEach(item => {
            if (item.md_comics) {
                const comic = item.md_comics;
                const key = comic.slug || comic.title;
                if (key) {
                    comicCountryMap.set(key, comic.country);
                }
            }
        });
    }

    function addTypeColumnHeader() {
        const headerRow = document.querySelector('.flex.w-full.items-center.min-w-0:first-child');
        if (!headerRow || headerRow.querySelector('[data-comic-type-header]')) return;

        const typeHeader = document.createElement('div');
        typeHeader.className = 'text-center pl-3 flex items-center h-12 w-20 lg:w-24 font-semibold flex-none text-sm';
        typeHeader.setAttribute('data-comic-type-header', 'true');
        typeHeader.innerHTML = '<div class="w-full text-center">Type</div>';

        const lastReadColumn = headerRow.children[headerRow.children.length - 3];
        headerRow.insertBefore(typeHeader, lastReadColumn);
    }

    function addTypeColumnToRow(row) {
        if (row.querySelector('[data-comic-type-cell]')) return;

        const titleLink = row.querySelector('a[href*="/comic/"]');
        if (!titleLink) return;

        const href = titleLink.getAttribute('href');
        const slug = href.split('/comic/')[1];
        const country = comicCountryMap.get(slug);
        const comicType = country ? getComicTypeFromCountry(country) : 'Unknown';

        const typeCell = document.createElement('div');
        typeCell.className = 'flex justify-center text-sm md:text-base w-20 lg:w-24 flex-none';
        typeCell.setAttribute('data-comic-type-cell', 'true');
        typeCell.innerHTML = `<div class="w-full text-center">${comicType}</div>`;

        const lastReadColumn = row.children[row.children.length - 3];
        row.insertBefore(typeCell, lastReadColumn);
    }

    function processTable() {
        if (isProcessing) return;

        const rows = document.querySelectorAll('.flex.w-full.items-center.min-w-0');
        if (rows.length === 0) return;

        addTypeColumnHeader();

        rows.forEach((row, index) => {
            if (index === 0) return;
            addTypeColumnToRow(row);
        });
    }

    async function initializeScript() {
        if (isProcessing) return;
        isProcessing = true;

        const userId = extractUserIdFromUrl();
        if (!userId) {
            isProcessing = false;
            return;
        }

        const followsData = await fetchUserFollows(userId);
        buildComicCountryMap(followsData);

        processTable();

        isProcessing = false;
    }

    function observeChanges() {
        const observer = new MutationObserver((mutations) => {
            const hasRelevantChanges = mutations.some(mutation =>
                mutation.type === 'childList' &&
                Array.from(mutation.addedNodes).some(node =>
                    node.nodeType === 1 &&
                    (node.matches && node.matches('.flex.w-full.items-center.min-w-0') ||
                     node.querySelector && node.querySelector('.flex.w-full.items-center.min-w-0'))
                )
            );

            if (hasRelevantChanges) {
                setTimeout(processTable, 100);
            }
        });

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

    function handleNavigation() {
        let currentUrl = window.location.href;

        const checkForNavigation = () => {
            if (window.location.href !== currentUrl) {
                currentUrl = window.location.href;
                if (currentUrl.includes('/user/') && currentUrl.includes('/list')) {
                    setTimeout(initializeScript, 500);
                }
            }
        };

        setInterval(checkForNavigation, 1000);
    }

    setTimeout(initializeScript, 1000);
    observeChanges();
    handleNavigation();
})();