ZIP化!「260815_超かぐや姫おつかれさま本」

    @@ -46,18 +46,43 @@ }); document.documentElement.append(progress_container); const show_progress = page_number => progress_container.textContent = `ZIP: ${page_number} / ${max_page}`; + const canvas = document.createElement('canvas'); + Object.assign(canvas.style, { + display: 'none', + }); + document.documentElement.append(canvas); + const context = canvas.getContext('2d'); + const canvas_to_blob = () => new Promise(resolve => canvas.toBlob(blob => resolve(blob), 'image/png', 1)); + const get_img_filename = img_number => `${(''+img_number).padStart(max_page_strlen, 0)}${img_ext}`; const zip = new JSZip(); - for (let page=0; page < max_page; page++) { - const page_str = `${page+1}`.padStart(max_page_strlen, 0); - show_progress(page_str); - img_url.searchParams.set('page', page); + for (let page_number=0, img_number=1; page_number < max_page; page_number++) { + show_progress(page_number+1); + img_url.searchParams.set('page', page_number); const response = await fetch(img_url.href); if (! response.ok) { - console.error(`Fetch failure: page ${page_str}`); + console.error(`Fetch failure ${page_number+1}: ${img_url.href}`); continue; } const img_data = await response.blob(); - zip.file(`${page_str}${img_ext}`, img_data); + const img_bitmap = await createImageBitmap(img_data); + console.log(`${page_number+1}: ${img_bitmap.width}x${img_bitmap.height}`); + if (img_bitmap.width <= img_bitmap.height) { + zip.file(`${(''+img_number).padStart(max_page_strlen, 0)}${img_ext}`, img_data); + img_number++; + } + else { + const slice_width = Math.floor(img_bitmap.width / 2); + canvas.width = slice_width; + canvas.height = img_bitmap.height; + context.drawImage(img_bitmap, 0, 0, slice_width, img_bitmap.height, 0, 0, slice_width, img_bitmap.height); + zip.file(`${get_img_filename(img_number)}`, await canvas_to_blob()); + img_number++; + const slice_width2 = img_bitmap.width - slice_width; + canvas.width = slice_width2; + context.drawImage(img_bitmap, slice_width, 0, slice_width2, img_bitmap.height, 0, 0, slice_width2, img_bitmap.height); + zip.file(`${get_img_filename(img_number)}`, await canvas_to_blob()); + img_number++; + } } const blob = await zip.generateAsync({type: 'blob', compression: 'STORE'}); const download_link = document.createElement('a'); @@ -66,5 +91,6 @@ document.documentElement.append(download_link); download_link.click(); download_link.remove(); + canvas.remove(); progress_container.remove(); })();
  • /*
     * @title 「260815_超かぐや姫おつかれさま本」ダウンロード
     * @description Dropboxから「260815_超かぐや姫おつかれさま本」の画像をZIP形式でダウンロードする
     * @include https://www.dropbox.com/*
     * @license MIT License
     * @require 
     * @javascript_url
     */
    
    /*
    //「260815_超かぐや姫おつかれさま本」公開ページはこちら↓
    // https://www.dropbox.com/scl/fi/2dz2rnch41n0ym6svx2v1/260815_.pdf?rlkey=79hr9bq43shkosbwhe93003lu&st=z03mvt95&e=1&dl=0
    // ※関連ツイート(ポスト) → https://x.com/momocyo/status/2088566368677331068
    */
    
    (async () => {
        const wait = msec => new Promise(resolve => setTimeout(resolve, msec));
        const script = document.createElement('script');
        script.nonce = document.querySelector('script[nonce]').nonce;
        script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js';
        document.documentElement.append(script);
        for (let ci=0; ci < 50; ci++) {
            if (typeof JSZip !== 'undefined') break;
            await wait(100);
        }
        if (typeof JSZip === 'undefined') {
            alert('Failed to load JSZIP');
            return;
        }
        const document_title = document.querySelector('title').textContent;
        const max_page = document.querySelector('[data-testid="page-count"]').textContent.match(/\/\s*(\d+)\s*/)[1] - 0;
        const max_page_strlen = `${max_page}`.length;
        const sample_img = document.querySelector('[data-testid="preview-viewport"] [id^="pageContainer"][data-index] img');
        const img_url = new URL(sample_img.src);
        const img_ext = img_url.pathname.match(/\.[^.]+$/)[0];
        const progress_container = document.createElement('div');
        Object.assign(progress_container.style, {
            position: 'fixed',
            top: '4px',
            left: '8px',
            zIndex: 10000,
            padding: '4px',
            fontFamily: 'monospace',
            color: 'black',
            backgroundColor: 'lightyellow',
        });
        document.documentElement.append(progress_container);
        const show_progress = page_number => progress_container.textContent = `ZIP: ${page_number} / ${max_page}`;
        const canvas = document.createElement('canvas');
        Object.assign(canvas.style, {
            display: 'none',
        });
        document.documentElement.append(canvas);
        const context = canvas.getContext('2d');
        const canvas_to_blob = () => new Promise(resolve => canvas.toBlob(blob => resolve(blob), 'image/png', 1));
        const get_img_filename = img_number => `${(''+img_number).padStart(max_page_strlen, 0)}${img_ext}`;
        const zip = new JSZip();
        for (let page_number=0, img_number=1; page_number < max_page; page_number++) {
            show_progress(page_number+1);
            img_url.searchParams.set('page', page_number);
            const response = await fetch(img_url.href);
            if (! response.ok) {
                console.error(`Fetch failure ${page_number+1}: ${img_url.href}`);
                continue;
            }
            const img_data = await response.blob();
            const img_bitmap = await createImageBitmap(img_data);
            console.log(`${page_number+1}: ${img_bitmap.width}x${img_bitmap.height}`);
            if (img_bitmap.width <= img_bitmap.height) {
                zip.file(`${(''+img_number).padStart(max_page_strlen, 0)}${img_ext}`, img_data);
                img_number++;
            }
            else {
                const slice_width = Math.floor(img_bitmap.width / 2);
                canvas.width = slice_width;
                canvas.height = img_bitmap.height;
                context.drawImage(img_bitmap, 0, 0, slice_width, img_bitmap.height, 0, 0, slice_width, img_bitmap.height);
                zip.file(`${get_img_filename(img_number)}`, await canvas_to_blob());
                img_number++;
                const slice_width2 = img_bitmap.width - slice_width;
                canvas.width = slice_width2;
                context.drawImage(img_bitmap, slice_width, 0, slice_width2, img_bitmap.height, 0, 0, slice_width2, img_bitmap.height);
                zip.file(`${get_img_filename(img_number)}`, await canvas_to_blob());
                img_number++;
            }
        }
        const blob = await zip.generateAsync({type: 'blob', compression: 'STORE'});
        const download_link = document.createElement('a');
        download_link.href = URL.createObjectURL(blob);
        download_link.download = `${document_title.replace(/\.[^.]+$/,'')}.zip`;
        document.documentElement.append(download_link);
        download_link.click();
        download_link.remove();
        canvas.remove();
        progress_container.remove();
    })();
    
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。

History

  1. 2026/08/16 17:34:59 - 4 hours ago
  2. 2026/08/16 17:13:19 - 4 hours ago
  3. 2026/08/16 15:47:03 - 5 hours ago
  4. 2026/08/16 06:07:08 - 15 hours ago
  5. 2026/08/16 05:58:14 - 15 hours ago
  6. 2026/08/16 05:50:21 - 15 hours ago
  7. 2026/08/16 04:52:38 - 16 hours ago
  8. 2026/08/16 03:08:06 - 18 hours ago
  9. 2026/08/16 03:01:09 - 18 hours ago
  10. 2026/08/16 02:40:58 - 19 hours ago
  11. 2026/08/16 02:40:08 - 19 hours ago
  12. 2026/08/16 02:34:23 - 19 hours ago
  13. 2026/08/16 02:33:56 - 19 hours ago
  14. 2026/08/16 02:32:11 - 19 hours ago
  15. 2026/08/16 02:31:37 - 19 hours ago