Comick MangaUpdates Import

Import comics from MangaUpdates JSON export

目前為 2025-08-06 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Comick MangaUpdates Import
// @namespace    https://github.com/GooglyBlox
// @version      1.1
// @description  Import comics from MangaUpdates JSON export
// @author       GooglyBlox
// @match        https://comick.io/import
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    let observer;
    let buttonAdded = false;
    let isProcessing = false;

    function addMangaUpdatesButton() {
        if (buttonAdded || document.getElementById('mangaupdates-import-btn')) {
            return;
        }

        const importContainer = document.querySelector('.xl\\:container');
        if (!importContainer) return;

        const lastButtonContainer = importContainer.querySelector('.flex.items-center.mt-3:last-of-type');
        if (!lastButtonContainer) return;

        const mangaUpdatesButton = document.createElement('div');
        mangaUpdatesButton.className = 'flex items-center mt-3';
        mangaUpdatesButton.innerHTML = `
            <button id="mangaupdates-import-btn" class="btn flex w-44 justify-start">
                <img src="https://www.mangaupdates.com/images/manga-updates.svg" class="h-6 w-6 mx-2 rounded" alt="MangaUpdates">
                <div>MangaUpdates</div>
            </button>
            <input type="file" id="mangaupdates-file-input" accept=".json" style="display: none;">
        `;

        const readingListSelector = document.createElement('div');
        readingListSelector.className = 'flex items-center ml-4';
        readingListSelector.innerHTML = `
            <label for="reading-list-select" class="text-sm text-gray-300 mr-2">Add to:</label>
            <select id="reading-list-select" class="bg-gray-700 border border-gray-600 text-white text-sm rounded px-3 py-1 focus:outline-none focus:border-blue-500">
                <option value="1">Reading</option>
                <option value="2">Completed</option>
                <option value="3">On Hold</option>
                <option value="4">Dropped</option>
                <option value="5">Plan to Read</option>
            </select>
        `;

        mangaUpdatesButton.appendChild(readingListSelector);
        lastButtonContainer.insertAdjacentElement('afterend', mangaUpdatesButton);

        const progressSection = document.createElement('div');
        progressSection.id = 'mangaupdates-progress-section';
        progressSection.className = 'mt-4 hidden';
        progressSection.innerHTML = `
            <div class="p-4 bg-gray-800 rounded-lg border border-gray-600">
                <div class="flex justify-between text-sm text-gray-300 mb-2">
                    <span id="mangaupdates-progress-text">Processing MangaUpdates import...</span>
                    <span id="mangaupdates-progress-count">0/0</span>
                </div>
                <div class="w-full bg-gray-700 rounded-full h-2">
                    <div id="mangaupdates-progress-bar" class="bg-blue-600 h-2 rounded-full" style="width: 0%"></div>
                </div>
                <div id="mangaupdates-results" class="mt-4 max-h-64 overflow-y-auto"></div>
            </div>
        `;

        mangaUpdatesButton.insertAdjacentElement('afterend', progressSection);
        buttonAdded = true;

        setupEventListeners();
    }

    function setupEventListeners() {
        const importBtn = document.getElementById('mangaupdates-import-btn');
        const fileInput = document.getElementById('mangaupdates-file-input');

        if (!importBtn || !fileInput) return;

        importBtn.addEventListener('click', () => {
            if (!isProcessing) {
                fileInput.click();
            }
        });

        fileInput.addEventListener('change', async (e) => {
            const file = e.target.files[0];
            if (file && !isProcessing) {
                await processMangaUpdatesFile(file);
                e.target.value = '';
            }
        });
    }

    async function processMangaUpdatesFile(file) {
        isProcessing = true;
        const importBtn = document.getElementById('mangaupdates-import-btn');
        const progressSection = document.getElementById('mangaupdates-progress-section');

        importBtn.textContent = 'Processing...';
        importBtn.disabled = true;
        progressSection.classList.remove('hidden');

        try {
            const fileContent = await readFileAsText(file);
            const mangaUpdatesData = JSON.parse(fileContent);

            if (!Array.isArray(mangaUpdatesData)) {
                showMangaUpdatesError('Invalid MangaUpdates file format. Expected JSON array.');
                return;
            }

            await importFromMangaUpdates(mangaUpdatesData);

        } catch (error) {
            console.error('MangaUpdates import error:', error);
            showMangaUpdatesError('Error processing MangaUpdates file: ' + error.message);
        } finally {
            isProcessing = false;
            importBtn.disabled = false;
            importBtn.innerHTML = `
                <img src="https://www.mangaupdates.com/images/manga-updates.svg" class="h-6 w-6 mx-2 rounded" alt="MangaUpdates">
                <div>MangaUpdates</div>
            `;
        }
    }

    async function importFromMangaUpdates(mangaData) {
        const progressText = document.getElementById('mangaupdates-progress-text');
        const progressCount = document.getElementById('mangaupdates-progress-count');
        const progressBar = document.getElementById('mangaupdates-progress-bar');
        const resultsDiv = document.getElementById('mangaupdates-results');
        const readingListSelect = document.getElementById('reading-list-select');
        const selectedListType = parseInt(readingListSelect.value);
        const listName = readingListSelect.selectedOptions[0].textContent;

        const total = mangaData.length;
        let processed = 0;
        let successful = 0;
        let failed = 0;

        resultsDiv.innerHTML = `<div class="text-sm text-gray-300 mb-2 font-semibold">MangaUpdates Import Results (Adding to: ${listName}):</div>`;

        for (const manga of mangaData) {
            progressText.textContent = `Processing: ${manga.title}`;
            progressCount.textContent = `${processed}/${total}`;
            progressBar.style.width = `${(processed / total) * 100}%`;

            try {
                const searchResults = await searchComicOnComick(manga.title);

                if (searchResults && searchResults.length > 0) {
                    const bestMatch = searchResults[0];
                    const followResult = await followComic(bestMatch.id, selectedListType);

                    if (followResult.success) {
                        successful++;
                        addMangaUpdatesResultItem(manga.title, 'success', `Added to ${listName}: ${bestMatch.title}`);
                    } else {
                        failed++;
                        addMangaUpdatesResultItem(manga.title, 'error', `Failed to follow (Status: ${followResult.status})`);
                    }
                } else {
                    failed++;
                    addMangaUpdatesResultItem(manga.title, 'error', 'No matches found on Comick');
                }
            } catch (error) {
                failed++;
                addMangaUpdatesResultItem(manga.title, 'error', error.message);
            }

            processed++;
            await delay(200);
        }

        progressText.textContent = `MangaUpdates import complete: ${successful} successful, ${failed} failed`;
        progressCount.textContent = `${processed}/${total}`;
        progressBar.style.width = '100%';
    }

    async function searchComicOnComick(title) {
        const encodedTitle = encodeURIComponent(title);
        const response = await fetch(`https://api.comick.fun/v1.0/search/?page=1&limit=15&showall=false&q=${encodedTitle}&t=false`);

        if (!response.ok) {
            throw new Error(`Comick search failed: HTTP ${response.status}`);
        }

        return await response.json();
    }

    async function followComic(comicId, listType = 1) {
        try {
            const response = await fetch('https://api.comick.io/follow', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    id: comicId,
                    t: listType
                }),
                credentials: 'include'
            });

            return {
                success: response.ok,
                status: response.status,
                data: response.ok ? await response.json() : null
            };
        } catch (error) {
            console.error('Follow API error:', error);
            return { success: false, error: error.message };
        }
    }

    function addMangaUpdatesResultItem(title, type, message) {
        const resultsDiv = document.getElementById('mangaupdates-results');
        const resultItem = document.createElement('div');
        resultItem.className = `flex justify-between items-center py-1 px-2 text-sm rounded mb-1 ${getMangaUpdatesResultColor(type)}`;
        resultItem.innerHTML = `
            <span class="truncate flex-1 mr-2">${escapeHtml(title)}</span>
            <span class="text-xs">${escapeHtml(message)}</span>
        `;
        resultsDiv.appendChild(resultItem);
        resultsDiv.scrollTop = resultsDiv.scrollHeight;
    }

    function getMangaUpdatesResultColor(type) {
        switch (type) {
            case 'success': return 'text-green-400 bg-green-900/20';
            case 'error': return 'text-red-400 bg-red-900/20';
            default: return 'text-gray-400';
        }
    }

    function showMangaUpdatesError(message) {
        const resultsDiv = document.getElementById('mangaupdates-results');
        resultsDiv.innerHTML = `<div class="text-red-400 text-sm p-2 bg-red-900/20 rounded">${escapeHtml(message)}</div>`;
    }

    function readFileAsText(file) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onload = (e) => resolve(e.target.result);
            reader.onerror = () => reject(new Error('Failed to read file'));
            reader.readAsText(file);
        });
    }

    function delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    function escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }

    function checkAndAddMangaUpdatesButton() {
        const isImportPage = window.location.pathname === '/import';
        const hasContainer = document.querySelector('.xl\\:container');
        const hasImportContent = document.querySelector('h1') &&
                                document.querySelector('h1').textContent.includes('Import Your Comics');

        if (isImportPage && hasContainer && hasImportContent && !buttonAdded) {
            setTimeout(addMangaUpdatesButton, 100);
        } else if (!isImportPage) {
            buttonAdded = false;
            const existingBtn = document.getElementById('mangaupdates-import-btn');
            const existingProgress = document.getElementById('mangaupdates-progress-section');
            if (existingBtn) existingBtn.closest('.flex').remove();
            if (existingProgress) existingProgress.remove();
        }
    }

    function startObserver() {
        if (observer) {
            observer.disconnect();
        }

        observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                    checkAndAddMangaUpdatesButton();
                }
            });
        });

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

    function init() {
        checkAndAddMangaUpdatesButton();
        startObserver();

        window.addEventListener('popstate', () => {
            buttonAdded = false;
            setTimeout(checkAndAddMangaUpdatesButton, 200);
        });
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址