LayoutDownloaderAtt

Script Universal de CSS pro Ultimate Scrapper

Bu script direkt olarak kurulamaz. Başka scriptler için bir kütüphanedir ve meta yönergeleri içerir // @require https://update.greasyfork.org/scripts/585205/1865576/LayoutDownloaderAtt.js

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu betiği yüklemek için bir betik yöneticisi eklentisi yüklemeniz gerekecektir.

(Zaten bir betik yöneticim var, hadi yükleyelim!)

Advertisement:

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

Advertisement:

/*
 * Dependências:
 *
 * 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 LayoutDownloaderAtt = (({ JSZip, saveAs }) => {
    let maxNum = 0;
    let promiseCount = 0;
    let fulfillCount = 0;
    let isErrorOccurred = false;

    // Elementos
    let startNumInputElement = null;
    let endNumInputElement = null;
    let downloadButtonElement = null;
    let statusElement = null;
    let panelElement = null;
    let guiHost = null;
    let qualitySliderElement = null;
    let qualityValElement = null;

    // Estado
    let selectedExt = 'jpg';

    // svg icons
    const externalLinkSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentcolor" width="16" height="16" style="margin-left: 5px;"><path fill-rule="evenodd" d="M10.604 1h4.146a.25.25 0 01.25.25v4.146a.25.25 0 01-.427.177L13.03 4.03 9.28 7.78a.75.75 0 01-1.06-1.06l3.75-3.75-1.543-1.543A.25.25 0 0110.604 1zM3.75 2A1.75 1.75 0 002 3.75v8.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 12.25v-3.5a.75.75 0 00-1.5 0v3.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-8.5a.25.25 0 01.25-.25h3.5a.75.75 0 000-1.5h-3.5z"></path></svg>`;
    const reloadSVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentcolor" width="16" height="16" style="margin-left: 5px;"><path fill-rule="evenodd" d="M8 2.5a5.487 5.487 0 00-4.131 1.869l1.204 1.204A.25.25 0 014.896 6H1.25A.25.25 0 011 5.75V2.104a.25.25 0 01.427-.177l1.38 1.38A7.001 7.001 0 0114.95 7.16a.75.75 0 11-1.49.178A5.501 5.501 0 008 2.5zM1.705 8.005a.75.75 0 01.834.656 5.501 5.501 0 009.592 2.97l-1.204-1.204a.25.25 0 01.177-.427h3.646a.25.25 0 01.25.25v3.646a.25.25 0 01-.427.177l-1.38-1.38A7.001 7.001 0 011.05 8.84a.75.75 0 01.656-.834z"></path></svg>`;
    
    // Inicialização
    function init({
        maxImageAmount,
        getImagePromises,
        title = `package_${Date.now()}`,
        imageSuffix = 'jpg',
        zipOptions = {},
        positionOptions = {}
    }) {
        maxNum = maxImageAmount;
        selectedExt = imageSuffix;

        setupUI(positionOptions);
        setupUpdateNotification();

        if (downloadButtonElement) {
            downloadButtonElement.onclick = function () {
                if (!isOKToDownload()) return;
                this.disabled = true;
                this.textContent = "Processing...";
                this.style.backgroundColor = '#9ca3af';
                this.style.cursor = 'not-allowed';
                this.style.boxShadow = 'none';
                statusElement.innerHTML = "<span style='color:#3b82f6; font-weight:bold;'>Starting download...</span>";

                const qualityValue = Number(qualitySliderElement.value);
                download(getImagePromises, title, selectedExt, zipOptions, qualityValue);
            }
        }
    }

    function setupUI(positionOptions) {
        if (document.getElementById('cd-gui-container')) return;
        guiHost = document.createElement('div');
        guiHost.id = 'cd-gui-container';
        document.body.appendChild(guiHost);

        const shadow = guiHost.attachShadow({ mode: 'open' });

        const style = document.createElement('style');
        style.innerHTML = `
        :host { all: initial; }
        *, *::before, *::after { box-sizing: border-box; }
        #cd-panel { position: fixed; top: 20px; right: 20px; width: 340px; background: #ffffff; border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.12); z-index: 999999; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; padding: 20px; color: #333; box-sizing: border-box; user-select: none; }
        .cd-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
        .cd-title { font-weight: 700; color: #3b82f6; font-size: 16px; display: flex; align-items: center; gap: 8px; }
        .cd-close { border: none; background: none; font-size: 20px; color: #9ca3af; cursor: pointer; padding: 0; line-height: 1; transition: color 0.2s; font-weight: bold; }
        .cd-close:hover { color: #ef4444; }
        .cd-status { font-size: 14px; margin-bottom: 16px; color: #10b981; font-weight:bold; min-height: 40px; text-align: center; display: flex; align-items: center; justify-content: center; }
        .cd-box { border: 1px solid #e2e8f0; border-radius: 10px; padding: 16px; margin-bottom: 20px; }
        .cd-box-title { font-size: 11px; font-weight: 700; color: #3b82f6; text-transform: uppercase; text-align: center; margin-bottom: 12px; letter-spacing: 0.5px; }
        .cd-res-inputs { display: flex; justify-content: center; align-items: center; gap: 12px; }
        .cd-res-input { width: 80px; padding: 8px; border: 1px solid #e2e8f0; border-radius: 6px; background: #f8fafc; text-align: center; color: #64748b; font-weight: 600; font-size: 14px; outline: none; }
        .cd-res-input:focus { border-color: #3b82f6; }
        .cd-format-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 16px; }
        .cd-fmt-btn { border: 1px solid #e2e8f0; border-radius: 8px; padding: 12px 8px; background: #fff; cursor: pointer; text-align: center; transition: all 0.2s; color: #1f2937; font-weight: 600; font-size: 14px; display: flex; flex-direction: column; align-items: center; gap: 4px; }
        .cd-fmt-btn span { font-size: 11px; font-weight: 400; color: #6b7280; }
        .cd-fmt-btn.active { background: #3b82f6; border-color: #3b82f6; color: #fff; box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); }
        .cd-fmt-btn.active span { color: #dbeafe; }
        .cd-fmt-btn:disabled { opacity: 0.5; cursor: not-allowed; background: #f9fafb; }
        .cd-slider-container { margin-bottom: 20px; }
        .cd-slider-label { font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; margin-bottom: 8px; display: flex; justify-content: space-between; }
        .cd-slider { width: 100%; cursor: pointer; }
        .cd-main-btn { width: 100%; padding: 14px; border: none; border-radius: 8px; background: #2563eb; color: #fff; font-size: 15px; font-weight: 700; cursor: pointer; transition: background 0.2s; box-shadow: 0 4px 12px rgba(37, 99, 235, 0.2); margin-bottom: 16px; }
        .cd-main-btn:hover { background: #1d4ed8; }
        .cd-version { text-align: center; font-size: 11px; color: #9ca3af; margin-top: 16px; }
        .cd-warning { background: #fef9c3; border: 1px solid #fde047; border-radius: 8px; padding: 12px; color: #854d0e; font-size: 12px; text-align: center; line-height: 1.5; font-weight: 500; }
    `;
        shadow.appendChild(style);

        panelElement = document.createElement('div');
        panelElement.id = 'cd-panel';
        for (const [key, value] of Object.entries(positionOptions)) {
            if (key === 'top' || key === 'bottom' || key === 'left' || key === 'right') {
                panelElement.style[key] = value;
            }
        }

        panelElement.innerHTML = `
        <div class="cd-header">
            <div class="cd-title">🔥 Ultimate Scrapper</div>
            <button id="cd-close" class="cd-close">&times;</button>
        </div>
        <div id="cd-status" class="cd-status">Ready to download!</div>
        <div class="cd-box">
            <div class="cd-box-title">Page Range</div>
            <div class="cd-res-inputs">
                <input type="number" id="cd-start-num" class="cd-res-input" value="1" min="1" max="${maxNum}">
                <span style="color: #cbd5e1; font-size: 14px; font-weight: bold;">until</span>
                <input type="number" id="cd-end-num" class="cd-res-input" value="${maxNum}" min="1" max="${maxNum}">
            </div>
        </div>
        
        <div style="font-size: 11px; font-weight: 700; color: #94a3b8; text-transform: uppercase; margin-bottom: 12px;">Image's Output</div>
        <div class="cd-format-grid">
            <button class="cd-fmt-btn ${selectedExt === 'webp' ? 'active' : ''}" data-ext="webp">WebP</button>
            <button class="cd-fmt-btn ${selectedExt === 'jpg' ? 'active' : ''}" data-ext="jpg">JPEG</button>
            <button class="cd-fmt-btn ${selectedExt === 'png' ? 'active' : ''}" data-ext="png">PNG</button>
            <button class="cd-fmt-btn ${selectedExt === 'jxl' ? 'active' : ''}" data-ext="jxl">JXL</button>
        </div>

        <div class="cd-slider-container">
            <div class="cd-slider-label">
                <span>Quality:</span>
                <span style="color: #3b82f6;"><span id="cd-quality-val">100</span>%</span>
            </div>
            <input type="range" id="cd-quality" min="0" max="100" value="100" class="cd-slider">
        </div>

        <button id="cd-start" class="cd-main-btn">Download</button>
        <div class="cd-warning" style="margin-top:10px;">⚠️ When you download a chapter and, if by chance, you want to download another one right after, you <b>NEED</b> to reload the page. Otherwise, it'll download the manga you downloaded last.</div>
        <div class="cd-version">Powered By: Nkkz</div>
        `;
        shadow.appendChild(panelElement);

        startNumInputElement = shadow.getElementById('cd-start-num');
        endNumInputElement = shadow.getElementById('cd-end-num');
        downloadButtonElement = shadow.getElementById('cd-start');
        statusElement = shadow.getElementById('cd-status');
        
        qualitySliderElement = shadow.getElementById('cd-quality');
        qualityValElement = shadow.getElementById('cd-quality-val');

        startNumInputElement.onkeydown = (e) => e.stopPropagation();
        endNumInputElement.onkeydown = (e) => e.stopPropagation();

        endNumInputElement.addEventListener('input', (e) => {
            if (parseInt(e.target.value, 10) > maxNum) {
                e.target.value = maxNum;
            }
        });

        startNumInputElement.addEventListener('input', (e) => {
            if (parseInt(e.target.value, 10) > maxNum) {
                e.target.value = maxNum;
            }
        });

        qualitySliderElement.onkeydown = (e) => e.stopPropagation();

        qualitySliderElement.oninput = (e) => {
            qualityValElement.textContent = e.target.value;
        };

        shadow.getElementById('cd-close').onclick = () => { guiHost.remove(); };

        function updateSliderState(ext) {
            if (ext === 'jxl' || ext === 'png') {
                qualitySliderElement.disabled = true;
                qualitySliderElement.style.opacity = '0.4';
                qualitySliderElement.style.cursor = 'not-allowed';
                qualityValElement.style.opacity = "0.4";
            } else {
                qualitySliderElement.disabled = false;
                qualitySliderElement.style.opacity = '1';
                qualitySliderElement.style.cursor = 'pointer';
                qualityValElement.style.opacity = "1";
            }
        }
        updateSliderState(selectedExt);

        shadow.querySelectorAll('.cd-fmt-btn').forEach(btn => {
            btn.onclick = () => {
                shadow.querySelectorAll('.cd-fmt-btn').forEach(b => b.classList.remove('active'));
                btn.classList.add('active');
               
                selectedExt = btn.getAttribute('data-ext');
                updateSliderState(selectedExt);
            };
        });
    }

    async function setupUpdateNotification() {
        if (typeof GM_info === 'undefined' || typeof GM_xmlhttpRequest === 'undefined') return;
        const localVersion = Number(GM_info.script.version);

        const scriptID = (GM_info.script.homepageURL || GM_info.script.homepage).match(/scripts\/(?<id>\d+)-/)?.groups?.id;
        const scriptURL = `https://update.greasyfork.org/scripts/${scriptID}/raw.js`;
        const latestVersionString = await new Promise(resolve => {
            GM_xmlhttpRequest({
                method: 'GET',
                url: scriptURL,
                responseType: 'text',
                onload: res => resolve(res.response.match(/@version\s+(?<version>[0-9\.]+)/)?.groups?.version)
            });
        });

        const latestVersion = Number(latestVersionString);

        if (Number.isNaN(localVersion) || Number.isNaN(latestVersion)) return;
        if (latestVersion <= localVersion) return;

        const updateLinkElement = document.createElement('a');
        updateLinkElement.id = 'LayoutDownloaderAtt-UpdateLink';
        updateLinkElement.href = scriptURL.replace('raw.js', 'raw.user.js');
        updateLinkElement.innerHTML = `Update v${latestVersionString} is out! ${externalLinkSVG}`;
        updateLinkElement.style = `
      display: flex; justify-content: center; align-items: center;
      margin-top: 16px; padding: 12px; font-size: 13px; font-weight: bold;
      text-decoration: none; color: white; background-color: #10b981;
      border-radius: 8px; cursor: pointer; transition: background 0.2s;
    `;

        updateLinkElement.onmouseover = () => updateLinkElement.style.backgroundColor = '#059669';
        updateLinkElement.onmouseout = () => updateLinkElement.style.backgroundColor = '#10b981';
        updateLinkElement.onclick = () => setTimeout(() => {
            updateLinkElement.removeAttribute('href');
            updateLinkElement.innerHTML = `Please, reload the guide. ${reloadSVG}`;
            updateLinkElement.style.cursor = 'default';
            updateLinkElement.style.backgroundColor = '#f59e0b';
        }, 1000);
        panelElement.appendChild(updateLinkElement);
    }

    function isOKToDownload() {
        const startNum = Number(startNumInputElement.value);
        const endNum = Number(endNumInputElement.value);

        if (Number.isNaN(startNum) || Number.isNaN(endNum)) { alert("Please enter page number correctly."); return false; }
        if (!Number.isInteger(startNum) || !Number.isInteger(endNum)) { alert("Please enter integers correctly."); return false; }
        if (startNum < 1 || endNum < 1) { alert("Page number shouldn't smaller than 1."); return false; }
        if (startNum > maxNum || endNum > maxNum) { alert(`Page number shouldn't bigger than ${maxNum}.`); return false; }
        if (startNum > endNum) { alert("Number of start shouldn't bigger than number of end."); return false; }

        return true;
    }

    async function download(getImagePromises, title, imageSuffix, zipOptions, qualityValue) {
        const startNum = Number(startNumInputElement.value);
        const endNum = Number(endNumInputElement.value);
        promiseCount = endNum - startNum + 1;
        fulfillCount = 0;
        // reseta
        isErrorOccurred = false;

        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, imageSuffix, qualityValue));
                images = images.concat(result);
            } catch (error) {
                return;
            }
        }

        JSZip.defaults.date = new Date(Date.now() - (new Date()).getTimezoneOffset() * 60000);
        const zip = new JSZip();
        const zipTitle = title.replaceAll(/\/|\\|\:|\*|\?|\"|\<|\>|\|/g, '');
        const folder = zip.folder(zipTitle);

        for (const [index, image] of images.entries()) {
            const filename = `${String(startNum + index).padStart(maxNum >= 100 ? String(maxNum).length : 2, '0')}.${imageSuffix}`;
            folder.file(filename, image, zipOptions);
        }

        const zipProgressHandler = (metadata) => {
            downloadButtonElement.textContent = `Zipping...`;
            statusElement.innerHTML = `Compacting ZIP...ᅠ<b style="color:#3b82f6;">ᅠ${metadata.percent.toFixed()}%</b>`;
        }
        const content = await zip.generateAsync({ type: "blob" }, zipProgressHandler);

        saveAs(content, `${zipTitle}.zip`);

        downloadButtonElement.textContent = "Download Complete. (Download again?)";
        downloadButtonElement.style.backgroundColor = '#10b981';
        downloadButtonElement.style.cursor = 'pointer'; 
        downloadButtonElement.disabled = false; 
        statusElement.innerHTML = `<span style="color:#10b981; font-weight:bold;">Sucess! Enjoy it!</span>`;
    }

    function fulfillHandler(res) {
        if (!isErrorOccurred) {
            fulfillCount++;
            downloadButtonElement.textContent = `Processing...`;
            statusElement.innerHTML = `Downloading the page:ᅠ<b>${fulfillCount}/${promiseCount}</b>ᅠ(${Math.round((fulfillCount / promiseCount) * 100)}%)`;
        }
        return res;
    }

    function rejectHandler(err) {
        isErrorOccurred = true;
        console.error(err);

        downloadButtonElement.textContent = 'Error Occurred(Try again?)';
        downloadButtonElement.style.backgroundColor = '#ef4444';
        downloadButtonElement.style.cursor = 'pointer';
        downloadButtonElement.disabled = false; 
        statusElement.innerHTML = `<span style="color:#ef4444; font-weight:bold;">Error to download the page.</span>`;

        return Promise.reject(err);
    }

    return { init, fulfillHandler, rejectHandler };
})(window);