Dark Horse Downloader

Download a DRM-free copy of your Dark Horse digital comics.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Dark Horse Downloader
// @namespace    http://schiff.io/
// @version      0.3.1
// @description  Download a DRM-free copy of your Dark Horse digital comics.
// @author       Hayden Schiff (oxguy3)
// @match        https://digital.darkhorse.com/read/*
// @match        https://digital.darkhorse.com/bookshelf*
// @connect      cloudfront.net
// @grant        GM_xmlhttpRequest
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.3/jszip.js
// ==/UserScript==
/**
 * DARK HORSE DOWNLOADER
 * Made by Hayden Schiff (oxguy3), license is Creative Commons Zero.
 *
 * This script makes it really easy to download a DRM-free copy of your digital
 * comics on Dark Horse Digital. Simply go to your Dark Horse bookshelf at
 * <https://digital.darkhorse.com/bookshelf>, then disable "Stack by Series" and
 * switch to Gallery View if you haven't already. You will now see a "Download"
 * button below all your comics.
 *
 * When you click the download button for any of your comics, it'll open the
 * reader for that comic, then start downloading the comic. A red box in the
 * middle of the screen will show you the progress of the download (be warned
 * that things will be a bit laggy while it's downloading. Once it's done, a
 * .CBZ file will pop into your downloads (if you aren't familiar, a .CBZ file
 * is just a zip file full of JPGs. You can easily open it with a reader app
 * or convert to PDF or whatever).
 */

(function() {
    'use strict';

    var pagesDone = 0;
    var pagesTotal = 0;

    // from http://stackoverflow.com/a/10073761/992504
    function padToFive(number) {
        if (number <= 99999) {
            number = ("0000" + number).slice(-5);
        }
        return number;
    }

    // from http://stackoverflow.com/a/15832662/992504
    function downloadURI(uri, name) {
        var link = document.createElement("a");
        link.download = name;
        link.href = uri;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    }

    // This is part of a hack to use base64 to work around Greasemonkey's lack of support
    // for responseType:blob on XHRs. Source: http://stackoverflow.com/a/8781262/992504
    function customBase64Encode(inputStr) {
        var
        bbLen = 3,
            enCharLen = 4,
            inpLen = inputStr.length,
            inx = 0,
            jnx,
            keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789+/=",
            output = "",
            paddingBytes = 0;
        var
        bytebuffer = new Array(bbLen),
            encodedCharIndexes = new Array(enCharLen);

        while (inx < inpLen) {
            for (jnx = 0; jnx < bbLen; ++jnx) {
                /*--- Throw away high-order byte, as documented at:
              https://.mozilla.org/En/Using_XMLHttpRequest#Handling_binary_data
            */
                if (inx < inpLen) bytebuffer[jnx] = inputStr.charCodeAt(inx++) & 0xff;
                else bytebuffer[jnx] = 0;
            }

            /*--- Get each encoded character, 6 bits at a time.
            index 0: first  6 bits
            index 1: second 6 bits
                        (2 least significant bits from inputStr byte 1
                         + 4 most significant bits from byte 2)
            index 2: third  6 bits
                        (4 least significant bits from inputStr byte 2
                         + 2 most significant bits from byte 3)
            index 3: forth  6 bits (6 least significant bits from inputStr byte 3)
        */
            encodedCharIndexes[0] = bytebuffer[0] >> 2;
            encodedCharIndexes[1] = ((bytebuffer[0] & 0x3) << 4) | (bytebuffer[1] >> 4);
            encodedCharIndexes[2] = ((bytebuffer[1] & 0x0f) << 2) | (bytebuffer[2] >> 6);
            encodedCharIndexes[3] = bytebuffer[2] & 0x3f;

            //--- Determine whether padding happened, and adjust accordingly.
            paddingBytes = inx - (inpLen - 1);
            switch (paddingBytes) {
                case 1:
                    // Set last character to padding char
                    encodedCharIndexes[3] = 64;
                    break;
                case 2:
                    // Set last 2 characters to padding char
                    encodedCharIndexes[3] = 64;
                    encodedCharIndexes[2] = 64;
                    break;
                default:
                    break; // No padding - proceed
            }

            /*--- Now grab each appropriate character out of our keystring,
            based on our index array and append it to the output string.
        */
            for (jnx = 0; jnx < enCharLen; ++jnx)
                output += keyStr.charAt(encodedCharIndexes[jnx]);
        }
        return output;
    }

    function updatePagesDoneCounter() {
        if (pagesDone % 5 === 0) {
            document.getElementById("dhdl_status").textContent = "Downloading " + pagesDone + " of " + pagesTotal + " pages...";
        }
    }

    // add download buttons to the bookshelf
    if (window.location.pathname == "/bookshelf") {
        // TODO: check if "stack by series" is enabled, and if so, don't add the download buttons
        var books = document.getElementsByClassName("bookshelf-item");
        for (var i = 0; i < books.length; i++) {
            var book = books[i];
            var btn = document.createElement("a");
            btn.setAttribute("href", "/read/" + book.id + "#download");
            btn.setAttribute("class", "button blue collection-read");
            btn.setAttribute("style", "margin-top: 6px;");
            btn.textContent = "Download";
            book.appendChild(btn);
        }

    } else if (window.location.pathname.startsWith("/read/")) {

        if (window.location.hash == "#download") {

            // create box for showing status of download
            var box = document.createElement("div");
            box.id = "dhdl_box";
            box.setAttribute("style", "background-color:#ffaaaa; border:2px solid #111; border-radius:5px; opacity:0.9; text-align:center; font-size:22px; width:400px; height:150px; margin:auto; position:absolute; top:0; left:0; bottom:0; right:0; z-index:10000;");

            var boxTitle = document.createElement("h1");
            boxTitle.textContent = "Dark Horse Downloader";
            box.appendChild(boxTitle);

            var boxStatus = document.createElement("p");
            boxStatus.id = "dhdl_status";
            boxStatus.textContent = "Starting up...";
            box.appendChild(boxStatus);

            document.body.appendChild(box);


            var bookTitle = document.getElementById("bookreader-title").getElementsByTagName("h2")[0].textContent;
            var bookTitleSafe = bookTitle.replace(/\s+/gi, ' ').replace(/[^a-z0-9 ]/gi, '_');

            var pageUrls = [];
            for (var i = 0; i < book_manifest.pages.length; i++) {
                var page = book_manifest.pages[i];
                var url = window.location.protocol + "//" + window.location.host + book_manifest.base_url + page.src_image;
                pageUrls.push(url);
            }
            pagesTotal = book_manifest.pages.length;
            updatePagesDoneCounter();

            // reverse order for right-to-left books (i.e. manga)
            if (book_manifest.is_rtl) {
                pageUrls.reverse();
            }

            var zip = new JSZip();

            Promise.all(pageUrls.map(function(url, index) {
                return new Promise(function(resolve) {
                    GM_xmlhttpRequest({
                        method: "GET",
                        url: url,
                        onload: function(response) {
                            zip.file(padToFive(index) + ".jpg", customBase64Encode(response.responseText), {
                                base64: true
                            });
                            pagesDone++;
                            updatePagesDoneCounter();
                            resolve();
                        },
                        overrideMimeType: 'text/plain; charset=x-user-defined'
                    });
                });
            })).then(function() {
                document.getElementById("dhdl_status").textContent = "Creating zip file...";
                zip.generateAsync({
                    type: "blob"
                }).then(function(content) {
                    document.getElementById("dhdl_status").textContent = "Done!";
                    downloadURI(URL.createObjectURL(content), bookTitleSafe + ".cbz");
                    setTimeout(function() {
                        document.body.removeChild(box);
                    }, 3000);
                });
            });
        }
    }
})();