您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
WEBのダウンロードライブラリ
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greatest.deepsurf.us/scripts/528949/1559807/LibImgDown.js
/* * Dependencies: * GM_info(optional) * Docs: https://violentmonkey.github.io/api/gm/#gm_info * GM_xmlhttpRequest(optional) * Docs: https://violentmonkey.github.io/api/gm/#gm_xmlhttprequest * JSZIP * Github: https://github.com/Stuk/jszip * CDN: https://unpkg.com/[email protected]/dist/jszip.min.js * FileSaver * Github: https://github.com/eligrey/FileSaver.js * CDN: https://unpkg.com/[email protected]/dist/FileSaver.min.js */ ; const ImageDownloader = (({ JSZip, saveAs }) => { let maxNum = 0; let promiseCount = 0; let fulfillCount = 0; let isErrorOccurred = false; let createFolder = false; let folderName = "images"; let zipFileName = "download.zip"; let zip = null; // ZIPオブジェクトの初期化 let imageDataArray = []; //imageDataArrayの初期化 // elements let startNumInputElement = null; let endNumInputElement = null; let downloadButtonElement = null; let panelElement = null; let folderRadioYes = null; let folderRadioNo = null; let folderNameInput = null; let zipFileNameInput = null; // 初期化関数 function init({ maxImageAmount, getImagePromises, title = `package_${Date.now()}`, WidthText = 0, HeightText = 0, imageSuffix = 'jpg', zipOptions = {}, positionOptions = {} }) { // 値を割り当てる maxNum = maxImageAmount; // UIをセットアップする setupUI(positionOptions, title, WidthText, HeightText); // ダウンロードボタンにクリックイベントリスナーを追加 downloadButtonElement.onclick = function () { if (!isOKToDownload()) return; this.disabled = true; this.textContent = "処理中"; // Processing → 処理中 this.style.backgroundColor = '#aaa'; this.style.cursor = 'not-allowed'; download(getImagePromises, title, imageSuffix, zipOptions); }; } // スタイルを定義 const style = document.createElement('style'); style.textContent = ` .input-element { box-sizing: content-box; padding: 1px 1px; width: 34px; height: 26px; border: 1px solid #aaa; border-radius: 4px; font-family: 'Consolas', 'Monaco'; font-size: 14px; text-align: center; } .button-element { margin-top: 8px; margin-left: auto; width: 128px; height: 48px; padding: 5px 5px; display: block; justify-content: center; align-items: center; font-size: 14px; font-family: 'BIZ UDPゴシック', 'Arial'; color: #fff; line-height: 1.2; background-color: #0984e3; border: 3px solidrgb(0, 0, 0); border-radius: 4px; cursor: pointer; } .toggle-button { position: fixed; top: 45px; left: 5px; z-index: 999999999; padding: 2px 5px; font-size: 14px; font-weight: bold; font-family: 'Monaco', 'Microsoft YaHei'; color: #fff; background-color: #000000; border: 1px solid #aaa; border-radius: 4px; cursor: pointer; } .panel-element { position: fixed; top: 80px; left: 5px; z-index: 999999999; box-sizing: border-box; padding: 0px; width: auto; min-width: 400px; max-width: 600px; height: auto; display: none; flex-direction: column; justify-content: center; align-items: baseline; font-size: 14px; font-family: 'Consolas', 'Monaco', 'Microsoft YaHei'; letter-spacing: normal; background-color: #f1f1f1; border: 1px solid #aaa; border-radius: 4px; } .range-container, .radio-container { display: flex; justify-content: center; align-items: baseline; } .textarea-element { box-sizing: content-box; padding: 1px 0px; width: 99%; min-height: 45px; max-height: 200px; border: 1px solid #aaa; border-radius: 1px; font-size: 11px; font-family: 'Consolas', 'Monaco', 'Microsoft YaHei'; text-align: left; resize: vertical; height: auto; } .to-span { margin: 0 6px; color: black; line-height: 1; word-break: keep-all; user-select: none; } `; document.head.appendChild(style); // UIセットアップ関数 function setupUI(positionOptions, title, WidthText, HeightText) { title = sanitizeFileName(title); // トグルボタンの作成 const toggleButton = document.createElement('button'); toggleButton.id = 'ImageDownloader-ToggleButton'; toggleButton.className = 'toggle-button'; toggleButton.textContent = 'UI OPEN'; document.body.appendChild(toggleButton); let isUIVisible = false; // 初期状態を非表示に設定 function toggleUI() { if (isUIVisible) { panelElement.style.display = 'none'; toggleButton.textContent = 'UI OPEN'; } else { panelElement.style.display = 'flex'; toggleButton.textContent = 'UI CLOSE'; } isUIVisible = !isUIVisible; } toggleButton.addEventListener('click', toggleUI); // パネル要素の作成 panelElement = document.createElement('div'); panelElement.id = 'ImageDownloader-Panel'; panelElement.className = 'panel-element'; // 「positionOptions」に従ってパネルの位置を変更する。 for (const [key, value] of Object.entries(positionOptions)) { if (key === 'top' || key === 'bottom' || key === 'left' || key === 'right') { panelElement.style[key] = value; } } // 開始番号入力欄の作成 startNumInputElement = document.createElement('input'); startNumInputElement.id = 'ImageDownloader-StartNumInput'; startNumInputElement.className = 'input-element'; startNumInputElement.type = 'text'; startNumInputElement.value = 1; // 終了番号入力欄の作成 endNumInputElement = document.createElement('input'); endNumInputElement.id = 'ImageDownloader-EndNumInput'; endNumInputElement.className = 'input-element'; endNumInputElement.type = 'text'; endNumInputElement.value = maxNum; // キーボード入力がブロックされないようにする startNumInputElement.onkeydown = (e) => e.stopPropagation(); endNumInputElement.onkeydown = (e) => e.stopPropagation(); // 「to」スパン要素の作成 const toSpanElement = document.createElement('span'); toSpanElement.id = 'ImageDownloader-ToSpan'; toSpanElement.className = 'to-span'; toSpanElement.textContent = 'から'; // to → から // ダウンロードボタン要素の作成 downloadButtonElement = document.createElement('button'); downloadButtonElement.id = 'ImageDownloader-DownloadButton'; downloadButtonElement.className = 'button-element'; downloadButtonElement.textContent = 'ダウンロード'; // Download → ダウンロード // 範囲入力コンテナ要素の作成 const rangeInputContainerElement = document.createElement('div'); rangeInputContainerElement.id = 'ImageDownloader-RangeInputContainer'; rangeInputContainerElement.className = 'range-container'; // ラジオボタンコンテナ要素の作成 const rangeInputRadioElement = document.createElement('div'); rangeInputRadioElement.id = 'ImageDownloader-RadioChecker'; rangeInputRadioElement.className = 'radio-container'; // ラジオボタンを作成(フォルダ選択用'YES') folderRadioYes = document.createElement('input'); folderRadioYes.type = 'radio'; folderRadioYes.name = 'createFolder'; folderRadioYes.value = 'yes'; folderRadioYes.id = 'createFolderYes'; // ラジオボタンを作成(フォルダ選択用'No') folderRadioNo = document.createElement('input'); folderRadioNo.type = 'radio'; folderRadioNo.name = 'createFolder'; folderRadioNo.value = 'no'; folderRadioNo.id = 'createFolderNo'; folderRadioNo.checked = true; // フォルダ名入力欄の作成 folderNameInput = document.createElement('textarea'); folderNameInput.id = 'folderNameInput'; folderNameInput.className = 'textarea-element'; folderNameInput.value = title; // 初期値としてタイトルを使用 folderNameInput.disabled = true; // ZIPファイル名入力欄の作成 zipFileNameInput = document.createElement('textarea'); zipFileNameInput.id = 'zipFileNameInput'; zipFileNameInput.className = 'textarea-element'; zipFileNameInput.value = `${title}.zip`; // titleを使用してZIPファイル名を設定 // ラジオボタンのイベントリスナーを追加 folderRadioYes.addEventListener('change', () => { createFolder = true; folderNameInput.disabled = false; // フォルダ名入力欄を有効化 }); folderRadioNo.addEventListener('change', () => { createFolder = false; folderNameInput.disabled = true; // フォルダ名入力欄を無効化 }); // 組み立ててドキュメントに挿入 rangeInputContainerElement.appendChild(startNumInputElement); rangeInputContainerElement.appendChild(toSpanElement); rangeInputContainerElement.appendChild(endNumInputElement); panelElement.appendChild(rangeInputContainerElement); rangeInputRadioElement.appendChild(document.createTextNode('フォルダ作成:')); rangeInputRadioElement.appendChild(folderRadioYes); rangeInputRadioElement.appendChild(document.createTextNode('する ')); rangeInputRadioElement.appendChild(folderRadioNo); rangeInputRadioElement.appendChild(document.createTextNode('しない')); panelElement.appendChild(rangeInputRadioElement); panelElement.appendChild(document.createTextNode('フォルダ名: ')); panelElement.appendChild(folderNameInput); panelElement.appendChild(document.createTextNode('ZIPファイル名: ')); panelElement.appendChild(zipFileNameInput); // サイズ情報の追加(条件付き) if (WidthText > 0 && HeightText > 0) { panelElement.appendChild(document.createTextNode(` サイズ: ${WidthText} x ${HeightText}`)); } panelElement.appendChild(downloadButtonElement); document.body.appendChild(panelElement); } // ページ番号が正しいか確認する関数 function isOKToDownload() { const startNum = Number(startNumInputElement.value); const endNum = Number(endNumInputElement.value); if (Number.isNaN(startNum) || Number.isNaN(endNum)) { alert("正しい値を入力してください。\nPlease enter page numbers correctly."); return false; } if (!Number.isInteger(startNum) || !Number.isInteger(endNum)) { alert("ページ番号は整数である必要があります。\nPage numbers must be integers."); return false; } if (startNum < 1 || endNum < 1) { alert("ページ番号は1以上である必要があります。\nPage numbers must be greater than or equal to 1."); return false; } if (startNum > maxNum || endNum > maxNum) { alert(`ページ番号は最大値(${maxNum})以下である必要があります。\nPage numbers must not exceed ${maxNum}.`); return false; } if (startNum > endNum) { alert("開始ページ番号は終了ページ番号以下である必要があります。\nStart page number must not exceed end page number."); return false; } return true; // 全ての条件が満たされている場合、trueを返す } // ダウンロード処理の開始 async function download(getImagePromises, title, imageSuffix, zipOptions) { const startNum = Number(startNumInputElement.value); const endNum = Number(endNumInputElement.value); promiseCount = endNum - startNum + 1; // 画像のダウンロードを開始、同時リクエスト数の上限は4 let images = []; for (let num = startNum; num <= endNum; num += 4) { const from = num; const to = Math.min(num + 3, endNum); try { const result = await Promise.all(getImagePromises(from, to)); images = images.concat(result); } catch (error) { return; // cancel downloading } } // ZIPアーカイブのファイル構造を設定 JSZip.defaults.date = new Date(Date.now() - (new Date()).getTimezoneOffset() * 60000); zip = new JSZip(); const { folderName, zipFileName } = sanitizeInputs(folderNameInput, zipFileNameInput); if (createFolder) { const folder = zip.folder(folderName); for (const [index, image] of images.entries()) { const filename = `${String(index + 1).padStart(3, '0')}.${imageSuffix}`; folder.file(filename, image, zipOptions); } } else { for (const [index, image] of images.entries()) { const filename = `${String(index + 1).padStart(3, '0')}.${imageSuffix}`; zip.file(filename, image, zipOptions); } } // ZIP化を開始し、進捗状況を表示 const zipProgressHandler = (metadata) => { downloadButtonElement.innerHTML = `ZIP書庫作成中(${metadata.percent.toFixed()}%)`; }; const content = await zip.generateAsync({ type: "blob" }, zipProgressHandler); // 「名前を付けて保存」ウィンドウを開く saveAs(content, zipFileName); // 全て完了 downloadButtonElement.textContent = "完了しました"; // Completed → 完了しました downloadButtonElement.disabled = false; downloadButtonElement.style.backgroundColor = '#0984e3'; downloadButtonElement.style.cursor = 'pointer'; } // ファイル名整形用の関数 function sanitizeFileName(str) { return str.trim() // 全角英数字を半角に変換 .replace(/[A-Za-z0-9]/g, s => String.fromCharCode(s.charCodeAt(0) - 0xFEE0)) // 連続する空白(全角含む)を半角スペース1つに統一 .replace(/[\s\u3000]+/g, ' ') // 「!?」または「?!」を「⁉」に置換 .replace(/[!?][!?]/g, '⁉') // 特定の全角記号を対応する半角記号に変換 .replace(/[!#$%&’,.()+-=@^_{}]/g, s => { const from = '!#$%&’,.()+-=@^_{}'; const to = "!#$%&',.()+-=@^_{}"; return to[from.indexOf(s)]; }) // ファイル名に使えない文字をハイフンに置換 .replace(/[\\/:*?"<>|]/g, '-'); } // folderNameとzipFileNameの整形処理関数 function sanitizeInputs(folderNameInput, zipFileNameInput) { const folderName = sanitizeFileName(folderNameInput.value); const zipFileName = sanitizeFileName(zipFileNameInput.value); return { folderName, zipFileName }; } // プロミスが成功した場合の処理 function fulfillHandler(res) { if (!isErrorOccurred) { fulfillCount++; downloadButtonElement.innerHTML = `処理中(${fulfillCount}/${promiseCount})`; } return res; } // プロミスが失敗した場合の処理 function rejectHandler(err) { isErrorOccurred = true; console.error(err); downloadButtonElement.textContent = 'エラーが発生しました'; // Error Occurred → エラーが発生しました downloadButtonElement.style.backgroundColor = 'red'; return Promise.reject(err); } return { init, fulfillHandler, rejectHandler }; })(window);