Bulk ZIP Upload & Collect Images as ZIP

Bulk upload JSONs, save images to ZIP, download all at once

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Bulk ZIP Upload & Collect Images as ZIP
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  Bulk upload JSONs, save images to ZIP, download all at once
// @match        https://cardconjurer.com/creator*
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    function waitForFileInput() {
        const fileInput = document.querySelector('input#file[type="file"][multiple][accept=".json"]');
        if (fileInput) {
            addBulkButton(fileInput);
        } else {
            setTimeout(waitForFileInput, 500);
        }
    }

    function addBulkButton(fileInput) {
        const container = document.createElement('div');
        container.style.marginTop = '20px';
        container.style.padding = '10px';
        container.style.borderTop = '2px dashed #ccc';
        container.style.textAlign = 'center';

        const bulkBtn = document.createElement('button');
        bulkBtn.textContent = '📦 Bulk Upload ZIP & Download All Images';
        bulkBtn.style.margin = '10px';
        bulkBtn.style.padding = '8px 16px';
        bulkBtn.style.border = '1px solid #999';
        bulkBtn.style.borderRadius = '6px';
        bulkBtn.style.background = '#f5f5f5';
        bulkBtn.style.fontSize = '14px';
        bulkBtn.style.cursor = 'pointer';

        const zipInput = document.createElement('input');
        zipInput.type = 'file';
        zipInput.accept = '.zip';
        zipInput.style.display = 'none';

        bulkBtn.addEventListener('click', () => {
            zipInput.click();
        });

        zipInput.addEventListener('change', async (e) => {
            if (!e.target.files.length) return;

            const zipFile = e.target.files[0];
            const jszip = new JSZip();
            const zip = await jszip.loadAsync(zipFile);

            const jsonFiles = Object.keys(zip.files).filter(name => name.toLowerCase().endsWith('.json'));

            const outputZip = new JSZip();

            for (const name of jsonFiles) {
                const content = await zip.files[name].async('blob');
                const jsonFile = new File([content], name, { type: "application/json" });

                // Simulate selecting this JSON file
                const dt = new DataTransfer();
                dt.items.add(jsonFile);
                fileInput.files = dt.files;
                fileInput.dispatchEvent(new Event('change', { bubbles: true }));

                // Wait until Save Image button appears
                const saveBtn = await waitForElement(() =>
                    Array.from(document.querySelectorAll('button'))
                        .find(btn => btn.innerText.trim().includes('Save Image')),
                    10000
                );

                if (!saveBtn) {
                    console.warn(`Save Image button not found for ${name}`);
                    continue;
                }

                // Get image URL from the "Save Image" button
                saveBtn.click();
                await new Promise(res => setTimeout(res, 3500));

                // Try to detect downloaded image from <a> or canvas
                let imgData = await getImageFromCanvasOrLink();
                if (imgData) {
                    outputZip.file(name.replace(/\.json$/i, '.png'), imgData, { base64: true });
                    console.log(`Added ${name.replace(/\.json$/i, '.png')} to ZIP`);
                } else {
                    console.warn(`Could not capture image for ${name}`);
                }
            }

            // Download ZIP
            const zipBlob = await outputZip.generateAsync({ type: "blob" });
            const a = document.createElement('a');
            a.href = URL.createObjectURL(zipBlob);
            a.download = 'all_cards.zip';
            a.click();
            console.log("All images zipped and downloaded.");
        });

        container.appendChild(bulkBtn);
        fileInput.closest('outline-card').after(container);
        document.body.appendChild(zipInput);
    }

    async function waitForElement(fn, timeout = 5000) {
        const start = Date.now();
        return new Promise(resolve => {
            (function check() {
                const el = fn();
                if (el) return resolve(el);
                if (Date.now() - start > timeout) return resolve(null);
                setTimeout(check, 200);
            })();
        });
    }

    async function getImageFromCanvasOrLink() {
        // Try <a download> approach
        const aTag = document.querySelector('a[download]');
        if (aTag && aTag.href.startsWith('data:image')) {
            return aTag.href.split(',')[1];
        }

        // Try canvas element
        const canvas = document.querySelector('canvas');
        if (canvas) {
            return canvas.toDataURL('image/png').split(',')[1];
        }

        return null;
    }

    waitForFileInput();
})();