☰

DoujinDesu Downloader

Download manga + WebP/JPG + ZIP

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey, το Greasemonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

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

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Violentmonkey για να εγκαταστήσετε αυτόν τον κώδικα.

θα χρειαστεί να εγκαταστήσετε μια επέκταση όπως το Tampermonkey ή το Userscripts για να εγκαταστήσετε αυτόν τον κώδικα.

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

Θα χρειαστεί να εγκαταστήσετε μια επέκταση διαχείρισης κώδικα χρήστη για να εγκαταστήσετε αυτόν τον κώδικα.

(Έχω ήδη έναν διαχειριστή κώδικα χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Έχω ήδη έναν διαχειριστή στυλ χρήστη, επιτρέψτε μου να τον εγκαταστήσω!)

// ==UserScript==
// @name         DoujinDesu Downloader
// @namespace    https://doujin.desu.xxx/
// @version      2.6
// @description  Download manga + WebP/JPG + ZIP
// @author       qxyraxp
// @license      MIT
// @match        https://doujin.desu.xxx/*
// @match        http://doujin.desu.xxx/*
// @grant        GM_download
// @grant        GM_xmlhttpRequest
// @run-at       document-start
// ==/UserScript==
(function () {
    'use strict';
    let imageURLs = [];
    let panelOpen = false;
    let autoScanInterval = null;

    // Base64 icons (circular 32x32)
    const ICON_FLOAT = 'data:image/png;base64,' + `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`;
    const ICON_SELECTED = 'data:image/png;base64,' + `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`;
    const ICON_ZIP = 'data:image/png;base64,' + `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`;

    // ==================== Minimal ZIP ====================
    function crc32(buf) {
        let c = ~0;
        for (let i = 0; i < buf.length; i++) {
            c ^= buf[i];
            for (let k = 0; k < 8; k++) c = (c >>> 1) ^ (0xedb88320 & -(c & 1));
        }
        return ~c >>> 0;
    }
    function createZip(files) {
        const localHeaders = [], fileDatas = [];
        let offset = 0;
        for (const f of files) {
            const nameBytes = new TextEncoder().encode(f.name);
            const data = f.data;
            const crc = crc32(data);
            const local = new Uint8Array(30 + nameBytes.length);
            const v = new DataView(local.buffer);
            v.setUint32(0, 0x04034b50, true);
            v.setUint16(4, 20, true);
            v.setUint16(8, 0, true);
            v.setUint32(14, crc, true);
            v.setUint32(18, data.length, true);
            v.setUint32(22, data.length, true);
            v.setUint16(26, nameBytes.length, true);
            local.set(nameBytes, 30);
            localHeaders.push({ header: local, offset, crc, size: data.length });
            fileDatas.push(data);
            offset += local.length + data.length;
        }
        const centralParts = [];
        let centralSize = 0;
        for (let i = 0; i < files.length; i++) {
            const h = localHeaders[i];
            const nameBytes = new TextEncoder().encode(files[i].name);
            const central = new Uint8Array(46 + nameBytes.length);
            const v = new DataView(central.buffer);
            v.setUint32(0, 0x02014b50, true);
            v.setUint16(4, 20, true);
            v.setUint16(6, 20, true);
            v.setUint32(16, h.crc, true);
            v.setUint32(20, h.size, true);
            v.setUint32(24, h.size, true);
            v.setUint16(28, nameBytes.length, true);
            v.setUint32(42, h.offset, true);
            central.set(nameBytes, 46);
            centralParts.push(central);
            centralSize += central.length;
        }
        const end = new Uint8Array(22);
        const ev = new DataView(end.buffer);
        ev.setUint32(0, 0x06054b50, true);
        ev.setUint16(8, files.length, true);
        ev.setUint16(10, files.length, true);
        ev.setUint32(12, centralSize, true);
        ev.setUint32(16, offset, true);
        const total = offset + centralSize + 22;
        const zip = new Uint8Array(total);
        let pos = 0;
        for (let i = 0; i < files.length; i++) {
            zip.set(localHeaders[i].header, pos); pos += localHeaders[i].header.length;
            zip.set(fileDatas[i], pos); pos += fileDatas[i].length;
        }
        for (const c of centralParts) { zip.set(c, pos); pos += c.length; }
        zip.set(end, pos);
        return new Blob([zip], { type: 'application/zip' });
    }
    // ==================== Ambil Judul ====================
    function getTitle() {
    try {
        const h1 = document.querySelector('h1.text-xl') ||
                   document.querySelector('h1.font-black') ||
                   document.querySelector('h1');
        if (h1) {
            let text = h1.textContent.trim()
                .replace(/[\\/:*?"<>|]/g, '')
                .replace(/\s+/g, ' ')
                .trim();
            if (text.length > 3) return text.slice(0, 80);
        }
        let pageTitle = document.title || '';
        pageTitle = pageTitle.replace(/\s*[-|–]\s*DoujinDesu.*$/i, '').trim();
        if (pageTitle.length > 3) {
            return pageTitle.replace(/[\\/:*?"<>|]/g, '').slice(0, 80);
        }
    } catch (e) {}
    return '';
}
    // ==================== Deteksi ====================
    function isTargetImage(url) {
        if (!url || typeof url !== 'string') return false;
        const u = url.toLowerCase();
        const isImageExt = u.includes('.webp') || u.includes('.jpg') || u.includes('.jpeg') || u.includes('.png');
        if (!isImageExt) return false;
        return (
            u.includes('amz-ch.desu.pics') ||
            u.includes('desu.pics') ||
            u.includes('x-amz-') ||
            u.includes('storage/uploads') ||
            /\/[a-z0-9]{18,}\.(webp|jpg|jpeg|png)/i.test(u)
        );
    }
    function addUrl(url) {
        if (!url || imageURLs.includes(url)) return false;
        if (isTargetImage(url)) {
            imageURLs.push(url);
            return true;
        }
        return false;
    }
    function scanXAmz() {
        let changed = false;
        const newUrls = [];
        document.querySelectorAll('img').forEach(img => {
            const candidates = [img.src, img.currentSrc, img.dataset.src];
            for (const src of candidates) {
                if (src && isTargetImage(src) && !newUrls.includes(src) && !imageURLs.includes(src)) {
                    newUrls.push(src);
                }
            }
        });
        try {
            performance.getEntriesByType('resource').forEach(entry => {
                if (isTargetImage(entry.name) && !newUrls.includes(entry.name) && !imageURLs.includes(entry.name)) {
                    newUrls.push(entry.name);
                }
            });
        } catch (e) {}
        if (newUrls.length > 0) {
            imageURLs.push(...newUrls);
            changed = true;
        }
        if (changed) updatePanel();
        return changed;
    }
    if ('PerformanceObserver' in window) {
        try {
            new PerformanceObserver((list) => {
                let changed = false;
                for (const entry of list.getEntries()) {
                    if (addUrl(entry.name)) changed = true;
                }
                if (changed) updatePanel();
            }).observe({ type: 'resource', buffered: true });
        } catch (e) {}
    }
    // ==================== UI ====================
    function createButton() {
        if (document.getElementById('xamz-open-button')) return;
        const btn = document.createElement('button');
        btn.id = 'xamz-open-button';
        btn.innerHTML = `<img src="${ICON_FLOAT}" style="width:22px;height:22px;vertical-align:middle;margin-right:6px;border-radius:50%;"> DoujinDesu DL`;
        Object.assign(btn.style, {
            position: 'fixed', right: '15px', bottom: '15px', zIndex: '2147483647',
            background: '#ff2d95', color: '#000', border: 'none', borderRadius: '8px',
            padding: '8px 14px', fontSize: '14px', fontWeight: 'bold', cursor: 'pointer',
            boxShadow: '0 3px 15px rgba(255,45,149,.5)', display: 'flex', alignItems: 'center'
        });
        btn.onclick = openPanel;
        document.body.appendChild(btn);
    }
    function openPanel() {
        if (panelOpen) return;
        panelOpen = true;
        scanXAmz();
        autoScanInterval = setInterval(() => {
            if (panelOpen) scanXAmz();
        }, 2000);
        const autoTitle = getTitle();
        const overlay = document.createElement('div');
        overlay.id = 'xamz-overlay';
        overlay.innerHTML = `
            <div id="xamz-box">
                <div id="xamz-header">
                    <div><b>📚 DoujinDesu Downloader by qxyraxp</b> <span id="xamz-count">0 gambar</span></div>
                    <button id="xamz-close">✕</button>
                </div>
                <div id="xamz-toolbar">
                    <button id="xamz-refresh">🔄 Refresh</button>
                    <button id="xamz-select-all">☑ Semua</button>
                    <button id="xamz-select-none">☐ Batal</button>
                    <button id="xamz-download-selected"><img src="${ICON_SELECTED}" style="width:18px;height:18px;vertical-align:middle;margin-right:5px;border-radius:50%;"> Download Terpilih</button>
                    <button id="xamz-download-zip"><img src="${ICON_ZIP}" style="width:18px;height:18px;vertical-align:middle;margin-right:5px;border-radius:50%;"> Download ZIP</button>
                </div>
                <div id="xamz-zipname-row">
                    <label>Nama ZIP:</label>
                    <input id="xamz-zipname" type="text" value="${autoTitle}" placeholder="Nama file ZIP...">
                </div>
                <div id="xamz-status"></div>
                <div id="xamz-grid"></div>
            </div>
        `;
        document.body.appendChild(overlay);
        createStyles();
        document.getElementById('xamz-close').onclick = closePanel;
        document.getElementById('xamz-refresh').onclick = () => { scanXAmz(); updatePanel(); };
        document.getElementById('xamz-select-all').onclick = () => {
            document.querySelectorAll('#xamz-grid input[type="checkbox"]').forEach(cb => cb.checked = true);
        };
        document.getElementById('xamz-select-none').onclick = () => {
            document.querySelectorAll('#xamz-grid input[type="checkbox"]').forEach(cb => cb.checked = false);
        };
        document.getElementById('xamz-download-selected').onclick = downloadSelected;
        document.getElementById('xamz-download-zip').onclick = downloadAsZip;
        updatePanel();
    }
    function closePanel() {
        document.getElementById('xamz-overlay')?.remove();
        panelOpen = false;
        if (autoScanInterval) {
            clearInterval(autoScanInterval);
            autoScanInterval = null;
        }
    }
    function createStyles() {
        if (document.getElementById('xamz-style')) return;
        const style = document.createElement('style');
        style.id = 'xamz-style';
        style.textContent = `
            #xamz-overlay{position:fixed;inset:0;z-index:2147483646;background:rgba(0,0,0,.85);font-family:Arial,sans-serif;color:#fff}
            #xamz-box{position:absolute;left:3%;right:3%;top:3%;bottom:3%;background:#0a0a0a;border:2px solid #ff2d95;border-radius:12px;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 0 30px rgba(255,45,149,.3)}
            #xamz-header{min-height:55px;background:#1a0011;display:flex;align-items:center;padding:0 15px;border-bottom:1px solid #ff2d95}
            #xamz-count{margin-left:10px;color:#ff9ec8;font-size:13px}
            #xamz-close{margin-left:auto;background:#ff2d95;color:#000;border:none;border-radius:6px;padding:7px 13px;cursor:pointer;font-weight:bold}
            #xamz-close:hover{background:#ff5caf}
            #xamz-toolbar{background:#12000c;padding:9px;display:flex;flex-wrap:wrap;gap:7px;border-bottom:1px solid #ff2d95}
            #xamz-toolbar button{background:#ff2d95;color:#000;border:1px solid #ff2d95;border-radius:6px;padding:8px 12px;cursor:pointer;font-weight:bold;display:flex;align-items:center}
            #xamz-toolbar button:hover{background:#ff5caf;border-color:#ff5caf}
            #xamz-zipname-row{background:#0f000a;padding:8px 12px;display:flex;align-items:center;gap:10px;border-bottom:1px solid #ff2d95}
            #xamz-zipname-row label{font-size:12px;color:#ff9ec8;white-space:nowrap}
            #xamz-zipname{flex:1;padding:7px 10px;border-radius:6px;border:1px solid #ff2d95;background:#1a0011;color:#fff;font-size:13px}
            #xamz-zipname:focus{outline:none;border-color:#ff5caf;box-shadow:0 0 0 2px rgba(255,45,149,.3)}
            #xamz-status{min-height:22px;padding:6px 12px;color:#ff9ec8;font-size:12px}
            #xamz-grid{flex:1;overflow:auto;padding:12px;display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:12px;align-content:start;background:#0a0a0a}
            .xamz-item{position:relative;background:#12000c;border:1px solid #ff2d95;border-radius:7px;padding:7px}
            .xamz-item:hover{border-color:#ff5caf;box-shadow:0 0 10px rgba(255,45,149,.4)}
            .xamz-item img{width:100%;height:250px;object-fit:contain;display:block;background:#000;border-radius:4px;cursor:pointer}
            .xamz-item label{display:flex;align-items:center;gap:7px;margin-top:7px;font-size:12px;color:#ff9ec8}
            .xamz-number{position:absolute;left:12px;top:12px;background:rgba(255,45,149,.9);color:#000;padding:3px 7px;border-radius:4px;font-size:12px;font-weight:bold}
        `;
        document.head.appendChild(style);
    }
    function updatePanel() {
        const grid = document.getElementById('xamz-grid');
        if (!grid) return;
        document.getElementById('xamz-count').textContent = imageURLs.length + ' gambar';
        grid.innerHTML = '';
        if (!imageURLs.length) {
            grid.innerHTML = `<div style="grid-column:1/-1;text-align:center;padding:50px;color:#ff9ec8">
                Scroll halaman sampai gambar muncul, lalu tekan Refresh
            </div>`;
            return;
        }
        imageURLs.forEach((url, index) => {
            const item = document.createElement('div');
            item.className = 'xamz-item';
            item.innerHTML = `
                <div class="xamz-number">${String(index + 1).padStart(3, '0')}</div>
                <img src="${url}" loading="eager" data-index="${index}">
                <label><input type="checkbox" checked data-index="${index}"> Panel ${index + 1}</label>
            `;
            const img = item.querySelector('img');
            const checkbox = item.querySelector('input[type="checkbox"]');
            img.addEventListener('click', () => {
                checkbox.checked = !checkbox.checked;
            });
            grid.appendChild(item);
        });
    }
    function getSelectedIndexes() {
        return [...document.querySelectorAll('#xamz-grid input:checked')].map(cb => +cb.dataset.index);
    }
    function downloadSelected() {
        const indexes = getSelectedIndexes();
        if (!indexes.length) return alert('Pilih dulu panelnya');
        startDownloads(indexes);
    }
    // ==================== ZIP ====================
    async function downloadAsZip() {
        const indexes = getSelectedIndexes();
        if (!indexes.length) return alert('Pilih dulu panelnya');
        let zipName = (document.getElementById('xamz-zipname')?.value || '').trim()
            .replace(/[\\/:*?"<>|]/g, '');
        if (!zipName) zipName = 'DoujinDesu';
        if (!zipName.toLowerCase().endsWith('.zip')) zipName += '.zip';
        const status = document.getElementById('xamz-status');
        const files = [];
        let ok = 0, fail = 0;
        status.textContent = `⏳ Mengambil gambar 0/${indexes.length}`;
        for (let i = 0; i < indexes.length; i++) {
            const idx = indexes[i];
            status.textContent = `⏳ Mengambil gambar ${i + 1}/${indexes.length}`;
            try {
                const blob = await fetchImageAsBlob(imageURLs[idx]);
                if (blob) {
                    const ext = getExtFromUrl(imageURLs[idx]);
                    const name = String(idx + 1).padStart(3, '0') + '.' + ext;
                    files.push({ name, data: new Uint8Array(await blob.arrayBuffer()) });
                    ok++;
                } else fail++;
            } catch (e) {
                fail++;
            }
            await new Promise(r => setTimeout(r, 120));
        }
        if (ok === 0) {
            status.textContent = '❌ Tidak ada gambar yang berhasil diambil';
            return;
        }
        status.textContent = '⏳ Membuat ZIP...';
        try {
            const zipBlob = createZip(files);
            const a = document.createElement('a');
            a.href = URL.createObjectURL(zipBlob);
            a.download = zipName;
            a.click();
            URL.revokeObjectURL(a.href);
            status.textContent = `✅ ZIP berhasil! ${ok} gambar${fail > 0 ? `, ${fail} gagal` : ''}`;
        } catch (e) {
            status.textContent = '❌ Gagal membuat ZIP';
        }
    }
    // ==================== Helper ====================
    function getExtFromUrl(url) {
        try {
            const pathname = new URL(url).pathname.toLowerCase();
            if (pathname.endsWith('.webp')) return 'webp';
            if (pathname.endsWith('.jpg') || pathname.endsWith('.jpeg')) return 'jpg';
            if (pathname.endsWith('.png')) return 'png';
        } catch (e) {}
        return 'jpg';
    }
    function fetchImageAsBlob(url) {
        return new Promise((resolve) => {
            const img = [...document.querySelectorAll('#xamz-grid img')].find(i => i.src === url);
            if (img && img.complete && img.naturalWidth > 0) {
                try {
                    const canvas = document.createElement('canvas');
                    canvas.width = img.naturalWidth;
                    canvas.height = img.naturalHeight;
                    canvas.getContext('2d').drawImage(img, 0, 0);
                    const ext = getExtFromUrl(url);
                    const mime = ext === 'webp' ? 'image/webp' : 'image/jpeg';
                    canvas.toBlob(b => resolve(b), mime, 0.92);
                    return;
                } catch (e) {}
            }
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                responseType: 'arraybuffer',
                headers: {
                    'Referer': location.href,
                    'Origin': location.origin,
                    'Accept': 'image/webp,image/jpeg,image/*,*/*;q=0.8'
                },
                onload: (res) => {
                    if (res.status >= 200 && res.status < 300) {
                        let mime = 'image/jpeg';
                        const ct = (res.responseHeaders || '').toLowerCase();
                        if (ct.includes('image/webp')) mime = 'image/webp';
                        else if (ct.includes('image/png')) mime = 'image/png';
                        resolve(new Blob([res.response], { type: mime }));
                    } else resolve(null);
                },
                onerror: () => resolve(null),
                ontimeout: () => resolve(null)
            });
        });
    }
    async function downloadOne(index) {
        const url = imageURLs[index];
        const ext = getExtFromUrl(url);
        const filename = String(index + 1).padStart(3, '0') + '.' + ext;
        try {
            const blob = await fetchImageAsBlob(url);
            if (blob) {
                const a = document.createElement('a');
                a.href = URL.createObjectURL(blob);
                a.download = filename;
                a.click();
                URL.revokeObjectURL(a.href);
                return true;
            }
        } catch (e) {}
        try {
            await new Promise((resolve, reject) => {
                GM_download({ url, name: filename, saveAs: false, onload: resolve, onerror: reject });
            });
            return true;
        } catch (e) {}
        return false;
    }
    function startDownloads(indexes) {
        const status = document.getElementById('xamz-status');
        let ok = 0, fail = 0;
        status.textContent = `⏳ 0/${indexes.length}`;
        (async () => {
            for (let i = 0; i < indexes.length; i++) {
                status.textContent = `⏳ ${i + 1}/${indexes.length}`;
                if (await downloadOne(indexes[i])) ok++;
                else fail++;
                await new Promise(r => setTimeout(r, 300));
            }
            status.textContent = `✅ Berhasil ${ok}, gagal ${fail}`;
        })();
    }
    function init() {
        if (!document.body) return setTimeout(init, 300);
        createButton();
        setTimeout(scanXAmz, 1500);
        setTimeout(scanXAmz, 4000);
    }
    init();
})();