// ==UserScript== // @name MyFigureCollection Image Downloader // @namespace https://kurotaku.de // @version 1.0 // @description Automatically/Manually download item page images from MFC as a ZIP or single files. // @author Kurotaku // @license CC BY-NC-SA 4.0 // @match https://myfigurecollection.net/* // @icon https://static.myfigurecollection.net/ressources/assets/webicon.png // @updateURL https://raw.githubusercontent.com/Kurotaku-sama/Userscripts/main/userscripts/MyFigureCollection_Image_Downloader/script.user.js // @downloadURL https://raw.githubusercontent.com/Kurotaku-sama/Userscripts/main/userscripts/MyFigureCollection_Image_Downloader/script.user.js // @require https://raw.githubusercontent.com/Kurotaku-sama/Userscripts/main/libraries/kuros_library.js // @require https://cdn.jsdelivr.net/npm/sweetalert2 // @grant GM_getValue // @grant GM_setValue // @grant GM_listValues // @grant GM_deleteValue // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_download // @grant GM_xmlhttpRequest // @connect static.myfigurecollection.net // @run-at document-end // ==/UserScript== let menu_ids = []; (async function() { const is_item_page = window.location.pathname.startsWith('/item/'); print("[MFC Downloader] Initializing..."); init_settings(); update_menu_commands(); if (is_item_page) { insert_download_button(); if (GM_getValue("mfc_autodownload", false)) { print("[MFC Downloader] Autodownload active..."); execute_logic(); } } })(); function init_settings() { const defaults = { "mfc_autodownload": false, "mfc_primary_only": false, "mfc_use_folders": true, "mfc_zip_subfolder": true, "mfc_use_item_id": true, "mfc_use_padding": true }; for (const [key, val] of Object.entries(defaults)) if (GM_getValue(key) === undefined) GM_setValue(key, val); } function update_menu_commands() { const is_item_page = window.location.pathname.startsWith('/item/'); menu_ids.forEach(id => GM_unregisterMenuCommand(id)); menu_ids = []; const auto = GM_getValue("mfc_autodownload"); const primary_only = GM_getValue("mfc_primary_only"); const use_folders = GM_getValue("mfc_use_folders"); const zip_sub = GM_getValue("mfc_zip_subfolder"); const use_item_id = GM_getValue("mfc_use_item_id"); const use_padding = GM_getValue("mfc_use_padding"); menu_ids.push(GM_registerMenuCommand(`Autodownload: ${auto ? "✅ ON" : "❌ OFF"}`, () => { GM_setValue("mfc_autodownload", !auto); update_menu_commands(); })); if(is_item_page) menu_ids.push(GM_registerMenuCommand(`Download Now 📥`, () => execute_logic())); menu_ids.push(GM_registerMenuCommand(`Mode: ${primary_only ? "🎯 Primary Only" : "🖼️ All Images"}`, () => { GM_setValue("mfc_primary_only", !primary_only); update_menu_commands(); })); menu_ids.push(GM_registerMenuCommand(`Format: ${use_folders ? "📁 ZIP" : "📄 Single Files"}`, () => { GM_setValue("mfc_use_folders", !use_folders); update_menu_commands(); })); if (use_folders) menu_ids.push(GM_registerMenuCommand(`ZIP Content: ${zip_sub ? "📂 Subfolder" : "📄 Direct files"}`, () => { GM_setValue("mfc_zip_subfolder", !zip_sub); update_menu_commands(); })); menu_ids.push(GM_registerMenuCommand(`Naming: ${use_item_id ? "🆔 Incl. Item ID" : "🔢 Just Numbers"}`, () => { GM_setValue("mfc_use_item_id", !use_item_id); update_menu_commands(); })); menu_ids.push(GM_registerMenuCommand(`Padding (01, 001): ${use_padding ? "✅ YES" : "❌ NO"}`, () => { GM_setValue("mfc_use_padding", !use_padding); update_menu_commands(); })); } function get_item_id() { const match = window.location.pathname.match(/\/item\/(\d+)/); return match ? match[1] : "unknown"; } async function execute_logic() { print("[MFC Downloader] Executing logic..."); if (GM_getValue("mfc_primary_only")) await download_primary_image(); else await download_all_images(); } async function download_primary_image() { const img = document.querySelector('.item-picture .main > img'); if (!img) return; const full_src = img.src.replace('/1/', '/2/'); const ext = get_extension(full_src); const item_id = get_item_id(); const file_name = GM_getValue("mfc_use_item_id") ? `${item_id}_primary.${ext}` : `primary.${ext}`; if (!GM_getValue("mfc_use_folders")) GM_download({ url: full_src, name: file_name }); else await download_images_zip([{url: full_src, name: file_name}], get_clean_folder_name()); } async function download_all_images() { print("[MFC Downloader] Fetching metadata..."); const meta = document.querySelector('meta[name="pictures"]'); if (!meta) { Swal.fire({ title: "Error", text: "Could not find image metadata. Try clicking the main image once.", icon: "error", theme: "dark", backdrop: true }); return; } const ordered_urls = get_ordered_urls(meta); const item_id = get_item_id(); const use_padding = GM_getValue("mfc_use_padding"); const pad_amount = ordered_urls.length.toString().length; const files = ordered_urls.map((url, i) => { const ext = get_extension(url); const index_str = use_padding ? String(i + 1).padStart(pad_amount, '0') : String(i + 1); const name = GM_getValue("mfc_use_item_id") ? `${item_id}_${index_str}.${ext}` : `${index_str}.${ext}`; return { url, name }; }); if (!GM_getValue("mfc_use_folders")) { for (const f of files) { GM_download({ url: f.url, name: f.name }); await sleep(150); } } else await download_images_zip(files, get_clean_folder_name()); } async function load_jszip() { if (typeof JSZip !== 'undefined') return; return new Promise(resolve => { const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js'; script.onload = resolve; document.head.appendChild(script); }); } async function download_images_zip(files, zip_name) { await load_jszip(); const zip = new JSZip(); const use_subfolder = GM_getValue("mfc_zip_subfolder", true); let progress = 0; const total = files.length; Swal.fire({ title: "Fetching images...", html: `Progress: 0/${total}`, allowOutsideClick: false, showConfirmButton: false, theme: "dark", backdrop: true, didOpen: () => Swal.showLoading() }); for (let f of files) { try { const blob = await fetch_image_blob(f.url); const zip_path = use_subfolder ? `${zip_name}/${f.name}` : f.name; zip.file(zip_path, blob); progress++; const prog_bar = document.getElementById('mfc-prog'); const prog_val = document.getElementById('mfc-val'); if (prog_bar) prog_bar.value = progress; if (prog_val) prog_val.innerText = progress; } catch (e) { print(`[MFC Downloader] Failed: ${f.name}`); } } const content = await zip.generateAsync({type: 'blob'}); const a = document.createElement('a'); a.href = URL.createObjectURL(content); a.download = `${zip_name}.zip`; document.body.appendChild(a); a.click(); document.body.removeChild(a); Swal.fire({ title: "Download Complete!", text: `${total} images have been packed into ${zip_name}.zip`, icon: "success", theme: "dark", timer: 3000, timerProgressBar: true, showConfirmButton: false, backdrop: true }); } function fetch_image_blob(url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url: url, responseType: "blob", onload: r => resolve(r.response), onerror: r => reject(r) }); }); } function get_ordered_urls(meta) { const pics = JSON.parse(decodeURIComponent(meta.content)); let urls = pics.map(p => String(p.src).replace('/1/', '/2/')); const main = document.querySelector(".item-picture .main img")?.src.replace('/1/', '/2/'); if (main) { const idx = urls.indexOf(main); if (idx > 0) { urls.splice(idx, 1); urls.unshift(main); } } return urls; } function get_extension(url) { let ext = url.split('.').pop().split('?')[0].toLowerCase(); return ext === 'jpeg' ? 'jpg' : ext; } function get_clean_folder_name() { const el = document.querySelector('#content h1.title'); return el ? el.innerText.replace(/[<>:"/\\|?*\u0000-\u001F]/g, '').trim() : 'MFC_Download'; } function insert_download_button() { wait_for_element("#wide .item-object .item-picture").then(container => { if (document.getElementById('mfc-download-btn')) return; container.parentNode.insertAdjacentHTML('beforeend', ``); document.getElementById('mfc-download-btn').addEventListener('click', () => execute_logic()); }); } GM_addStyle(` #mfc-download-btn { display: block; margin: auto; } `);