Bulk ZIP Upload & Collect Images as ZIP

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

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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();
})();