E-Hentai Automated Downloads

Automates downloads through the Doggie Bag Archiver

As of 2015-02-11. See the latest version.

// ==UserScript==
// @name           E-Hentai Automated Downloads
// @description    Automates downloads through the Doggie Bag Archiver
// @include        http://g.e-hentai.org/*
// @include        http://exhentai.org/*
// @include        http://*/*automatedEHDL=*
// @run-at         document-start
// @author         etc
// @version        1.2
// @namespace https://greasyfork.org/users/2168
// ==/UserScript==

var automating = (window.location.href.indexOf('automatedEHDL') != -1);

var addMetadata = function(url,metadata) {
    return url +
        (url.indexOf('?') != -1 ? '&' : '?') + 'automatedEHDL=' + metadata.nonce +
        '&automatedTorrent=' + (metadata.isTorrent ? 1 : 0);
};
       
var getMetadata = function(url) {
    var metadata = url.match(/automatedEHDL=(\d+).*automatedTorrent=(\d)/);
    return { nonce: metadata[1], isTorrent: metadata[2] == '1' };
};

if (automating) {

    var clear     = function() { for (var i=0;i<10;++i) clearTimeout(i); };
    var abort     = function(reason) { parent.postMessage({ type: 'failure', nonce: metadata.nonce, reason: reason },'*'); };
    var heartbeat = function() { parent.postMessage({ type: 'heartbeat', nonce: metadata.nonce },'*'); };

    var metadata = getMetadata(window.location.href), target;

    if (window.location.href.indexOf('archive') != -1)
        document.addEventListener('DOMSubtreeModified',clear,false);

    var onLoad = function() {

        for (var i=0;i<100;++i) clearTimeout(i);

        target = document.evaluate('.//a[starts-with(text(),"' + (metadata.isTorrent ? 'Torrent' : 'Archive') + ' Download")]',document,null,9,null).singleNodeValue;
        if (target !== null) {
            if (metadata.isTorrent && /\( 0 \)/.test(target.innerHTML)) abort('could not find any torrent');
            else {
                heartbeat();
                var url = target.getAttribute('onclick').match(/'(.+?)'/)[1];
                window.location.replace(addMetadata(url,metadata));
            }
            return;
        }

        // download confirmation popup
        target = document.evaluate('.//input[@value="Download Archive"]',document,null,9,null).singleNodeValue;
        if (target !== null) {
            heartbeat();
            target.parentNode.parentNode.action = addMetadata(target.parentNode.parentNode.action,metadata);
            target.click();
            return;
        }

        // server location popup
        target = document.evaluate('.//script[contains(text(),"gotonext")]',document,null,9,null).singleNodeValue;
        if (target !== null) {
            heartbeat();
            var url   = target.innerHTML.match(/document.location = "(.+?)"/)[1];
            var delay = target.innerHTML.match(/(\d+)\)/)[1];
            document.removeEventListener('DOMSubtreeModified',clear,false);
            setTimeout(function() { window.location.replace(addMetadata(url,metadata)); },parseInt(delay,10));
            return;
        }
        
        // download ready popup
        target = document.evaluate('.//a[text()="Click Here To Start Downloading"]',document,null,9,null).singleNodeValue;
        if (target !== null) {
            parent.postMessage({ type: 'success', nonce: metadata.nonce, url: target.href },'*');
            return;
        }

        // torrent page
        target = document.evaluate('.//input[@name="torrent_info"]',document,null,9,null).singleNodeValue;
        if (target != null) {
            var targets = Array.prototype.slice.call(document.querySelectorAll('[name="gtid"] + table'),0);
            targets = targets.map(function(x) {
                var link = x.querySelector('a').href;
                var data = x.textContent.match(/Seeds:\s*(\d+)(?:.|\n)*Peers:\s*(\d+)/);
                return { link: link, seeds: parseInt(data[1],10), peers: parseInt(data[2],10) };
            });
            targets = targets.sort(function(a,b) { return b.seeds - a.seeds; });
            if (targets.length === 0) abort('could not find any torrent');
            else if (targets[0].seeds === 0) abort('could not find any seeded torrent');
            else parent.postMessage({ type: 'success', nonce: metadata.nonce, url: targets[0].link },'*');
            return;
        }

        // we really shouldn't be here
        abort('No viable option found. Perhaps you\'re not logged in?\nCurrent URL: ' + window.location.href);

    };

    if (document.readyState == 'complete') onLoad();
    else window.addEventListener('load',onLoad,false);

} else {

    var downloadData = { }, downloadQueue = [ ];

    var loadingGIF =
        'R0lGODlhEgASAMQaAHl5d66urMXFw3l5dpSUk5WVlKOjoq+vrsbGw6Sko7u7uaWlpbm5t3h4doiIhtLSz4aGhJaWlsbGxNHRzrC' +
        'wr5SUkqKiobq6uNHRz4eHhf///wAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgAaACwAAAAAEgASAAAFaq' +
        'AmjmRplstyrkmbrCNFaUZtaFF0HvyhWRZNYVgwBY4BEmFJOB1NlYpJoYBpHI7RZXtZZb4ZEbd7AodFDIYVAjFJJCYA4ISoI0hyu' +
        'UnAF2geDxoDgwMnfBoYiRgaDQ1WiIqPJBMTkpYaIQAAIfkEBQoAGgAsAQABABAAEAAABWSgJo4aRZEoeaxHOiqKFsyBtizopV9y' +
        'nfwJ0o43MhgNKAYjZbGQJBLXKBLRIK4IaWFbEHgFUoKYoPFKRZUK6fFIORwojBxDytgzpDkdANDc8SQTExp8fBoQEGcDiwNnJA0' +
        'NLiEAACH5BAUKABoALAEAAQAQABAAAAVloCaOmqKQKHmtVzpKksa2FIUiOKIxjHb8B5JgKCAFjgHUMHUkPR6u0WKhwVgx0YQ2cc' +
        'W6DGCDZjKJiiwWEgCQikRQ6zWpQC+QBviBxuHQEP4EKA0NGhmGGRoVFWaHiGYjEBAuIQAAIfkEBQoAGgAsAQABABAAEAAABWSgJ' +
        'o6aJJEoiaxIOj6PJsyCpigopmNyff0X0o43AgZJk0mKwSABAK4RhaJ5PqOH7GHAHUQD4ICm0YiKwCSHI7VYoDLwDClBT5Di8khE' +
        'Y+gbUBAQGgWEBRoWFmYEiwRmJBUVLiEAACH5BAUKABoALAEAAQAQABAAAAVloCaO2vOQKImtWDoCgMa2koTCsDZNGuIjpIFwQBI' +
        'YBahGI2UkORyukUKhyVgz0Yv2csW6thcNBBIVMRikSCRFoaAK8ALpQD+QCHiCZrHQBP4BKBUVGgmGCX6BUQaMBmUkFhYuIQAAIf' +
        'kEBQoAGgAsAQABABAAEAAABWagJo4aAJAoaZrp6DjaIA/a86BZnmlNo2FADEm3GwWFJAgkNZmQIpHWSCLRFK4FKWKLIHgJUoFYo' +
        'KlUpCIxabFIKRSohDxButgvJIPeoKFQNHd4JBYWGgeHBxoMDGgBjgFoJI4tIQAAIfkEBQoAGgAsAQABABAAEAAABWSgJo6a45Ao' +
        'ma1ZOkaRxrYAgBZ4oUGQVtckgpBAGhgHqEol1WiQFgvX6PHQJK4JKWaLMXgNWq7GYpGKJhMShZKSSFCH+IGEqCNIgXxAo1BoBIA' +
        'CKHkaF4YXf4JSh4hmIwwMLiEAACH5BAUKABoALAEAAQAQABAAAAVloCaOWhSRKFmsRToui0bMhOY4aKInWlVpmWCGZCgaSMIhyW' +
        'JJQSAkCsU1AgA0h+yBarUGvgHqYDzQfKmiRoOkUKQeD9RlfiFh7hgSvS6RaPB5JAwMGgiGCBoTE2gCjQJoJI0uIQAAOw==';

    var icons = {
        download : 'M8.037,11.166L14.5,22.359c0.825,1.43,2.175,1.43,3,0l6.463-11.194c0.826-1.429,0.15-2.598-' +
            '1.5-2.598H9.537C7.886,8.568,7.211,9.737,8.037,11.166z',
        torrent  : 'M22.404,13.585c0-5.319-4.313-9.631-9.632-9.631c-5.32,0-9.632,4.313-9.632,9.631c0,4.1,2.5' +
            '67,7.593,6.177,8.982l-1.818-8.45l-0.514-2.388L6.075,7.505L9.6,6.746l1.303,6.059c0.352,1.636,1.0' +
            '94,2.514,2.316,2.25c0.967-0.208,1.377-0.995,1.487-1.597c0.049-0.228,0.013-0.51-0.047-0.786l-1.4' +
            '43-6.705L16.74,5.21l1.646,7.651c0.662,3.077,2.454,3.548,2.454,3.548s-2.419,0.521-3.433,0.738c-1' +
            '.012,0.219-1.694-1.591-1.694-1.591l-0.07,0.015c-0.288,0.785-0.613,2.06-3.127,2.602c-0.184,0.039' +
            '-0.364,0.064-0.542,0.083l1.064,4.948C18.232,23.063,22.404,18.814,22.404,13.585z',
        picker   : 'M22.727,18.242L4.792,27.208l8.966-8.966l-4.483-4.484l17.933-8.966l-8.966,8.966L22.727,18.242z',
        done     : 'M2.379,14.729 5.208,11.899 12.958,19.648 25.877,6.733 28.707,9.561 12.958,25.308z'
    };

    var getIcon = function(name,color) {
        return 'url("data:image/svg+xml,<svg viewBox=\'0 0 30 30\' preserveAspectRatio=\'true\' xmlns=\'http' +
            '://www.w3.org/2000/svg\'><path fill=\'' + color + '\' d=\'' + icons[name] + '\'/></svg>")';
    };

    var createButton = function(data) {
        var result = document.createElement(data.hasOwnProperty('type') ? data.type : 'a');
        if (data.hasOwnProperty('class')) result.className = data.class;
        if (data.hasOwnProperty('title')) result.title = data.title;
        if (data.hasOwnProperty('onClick')) result.addEventListener('click',data.onClick,false);
        if (data.hasOwnProperty('parent')) data.parent.appendChild(result);
        if (data.hasOwnProperty('target')) result.setAttribute('target',data.target);
        if (data.hasOwnProperty('style'))
            result.style.cssText = Object.keys(data.style).map(function(x) { return x + ': ' + data.style[x] + 'px'; }).join('; ');
        return result;
    };

    var start = function() {

        // button generation (thumbnail list)
        var thumbnails = document.querySelectorAll('.id3 > a'), n = thumbnails.length;
        while (n --> 0) {
            var bottom = Math.max(0,parseInt(thumbnails[n].parentNode.style.height,10) - thumbnails[n].firstChild.height);
            var right  = Math.max(0,0.5 * (200 - thumbnails[n].firstChild.width));
            createButton({ class: 'automatedButton downloadLink', title: 'Automated download', target: thumbnails[n].href,
                style: { bottom: bottom, right: right }, onClick: requestDownload, parent: thumbnails[n] });
            createButton({ class: 'automatedButton torrentLink', title: 'Torrent download', target: thumbnails[n].href,
                style: { bottom: bottom, left: 1 }, onClick: requestDownload, parent: thumbnails[n] });
        }

        // button generation (gallery)
        var bigThumbnail = document.querySelector('#gd1 > img');
        if (bigThumbnail !== null) {
            var bottom = bigThumbnail.parentNode.parentNode.clientHeight - bigThumbnail.offsetTop - bigThumbnail.height - 1;
            var right  = bigThumbnail.parentNode.parentNode.clientWidth - bigThumbnail.offsetLeft - bigThumbnail.width - 2;
            var left   = bigThumbnail.offsetLeft + 1;
            createButton({ class: 'automatedButton downloadLink', title: 'Automated download', target: window.location.href,
                style: { bottom: bottom, right: right }, onClick: requestDownload, parent: bigThumbnail.parentNode });
            createButton({ class: 'automatedButton torrentLink', title: 'Torrent download', target: window.location.href,
                style: { bottom: bottom, left: left }, onClick: requestDownload, parent: bigThumbnail.parentNode });
        }

        // button generation (row list)
        var rows = document.querySelectorAll('.it5 > a'), n = rows.length;
        while (n --> 0) {
            var div = createButton({ type: 'div', class: 'automatedPicker', onClick: requestDownload, parent: rows[n].parentNode });
            var picker = createButton({ type: 'div', parent: div });
            createButton({ type: 'div', class: 'automatedInline torrentLink', title: 'Torrent download', 
                target: rows[n].href, parent: picker });
            createButton({ type: 'div', class: 'automatedInline downloadLink', title: 'Automated download',
                target: rows[n].href, parent: picker });
        }

        // message listener
        window.addEventListener('message',function(message) {
            var data = message.data;
            if (!downloadData.hasOwnProperty(data.nonce)) return;
            if (data.type == 'heartbeat') onHeartbeat(data);
            else if (data.type == 'success') onSuccess(data);
            else onFailure(data);
        },false);

        // document style
        var style = document.createElement('style');
        style.innerHTML =
            '.automatedButton { display: none; position: absolute; text-align: left; cursor: pointer; padding: 8px;' +
                'color: white; margin-right: 1px; font-size: 20px; line-height: 11px; }' +
            '.downloadLink  { background-image: ' + getIcon('download','rgb(0,0,0)') + '; background-color: rgba(98,220,151,1); }' +
            '.torrentLink  { background-image: ' + getIcon('torrent','rgb(0,0,0)') + '; background-color: rgba(98,182,210,1); }' +
            '.torrentLink:not(.requested) { background-position: 2px 2px; }' +
            '.requested  { background-image: ' + getIcon('done','rgb(0,0,0)') + '; }' +
            '.requested, .working { background-color: rgba(255,143,113,1); }' +
            '.automatedButton.downloadLink  { border-radius: 0 0 5px 0 !important; width: 12px; height: 12px; }' +
            '.automatedButton.torrentLink  { border-radius: 0 0 0 5px !important; width: 12px; height: 12px; }' +
            '#gd1 > .automatedButton { border-radius: 0 0 0 0 !important; }' +
            '.working { background-image: url(data:image/gif;base64,' + loadingGIF + ') !important; background-repeat: no-repeat; }' +
            '.automatedInline.working { background-position: 3px 3px; }' +
            '.automatedButton.working { width: 18px; height: 18px; font-size: 0px; background-position: 5px 5px; padding: 5px !important; }' +
            '.automatedPicker { background-image: ' + getIcon('picker','rgb(252,0,97)') + '; width: 16px;' + 
                'height: 16px; float: left; cursor: pointer; }' +
            '.automatedButton:hover, .automatedInline:hover { background-color: rgba(255,199,139,1) !important; color: black !important; }' +
            '*:hover > .automatedButton, .automatedButton.working, .automatedButton.requested { display: block !important; }' +
            '.EHADiframe { width: 0px !important; height: 0px !important; opacity: 0 !important; }' +
            '.automatedPicker > div { display: none; z-index: 2; position: absolute; top: -4px; text-align: center; }' +
            '.automatedPicker:hover > div, .automatedPicker > div:hover { display: block; }' +
            '.automatedInline { padding: 3px; border: 1px solid black; width: 17px; height: 17px; display: inline-block; }' +
            '.automatedInline:first-child { border-right: none !important; }';
        document.head.appendChild(style);

    };

    var startDownload = function(url) {
        downloadQueue.push(url);
        doDownload(false);
    };

    var doDownload = function(force) {
        if (doDownload.inhibit && !force) return;
        if (downloadQueue.length === 0) {
            doDownload.inhibit = false;
            return;
        }
        doDownload.inhibit = true;
        var a = document.createElement('a');
        // don't use display: none as some versions of firefox don't like it
        a.style.cssText = 'width: 0px; height: 0px; overflow: hidden; white-space: nowrap; visibility: hidden;';
        a.href = downloadQueue.splice(0,1)[0];
        document.body.appendChild(a);
        a.click();
        document.body.removeChild(a);
        setTimeout(function() { doDownload(true); },1000);
    };

    var onHeartbeat = function(message) {
        generateTimeout(message.nonce);
    };

    var onSuccess = function(message) {
        var data = downloadData[message.nonce];
        document.body.removeChild(data.iframe);
        data.button.className = data.button.className.replace(/working/,'requested');
        clearTimeout(data.timeout);
        delete downloadData[message.nonce];
        startDownload(message.url);
    };

    var onFailure = function(message) {
        var data = downloadData[message.nonce];
        document.body.removeChild(data.iframe);
        data.button.className = data.button.className.replace(/\s?working/,'');
        clearTimeout(data.timeout);
        delete downloadData[data.nonce];
        alert('Something went wrong. Reason: ' + message.reason);
    };
    
    var generateTimeout = function(nonce) {
        if (!downloadData.hasOwnProperty(nonce)) return;
        clearTimeout(downloadData[nonce].timeout);
        downloadData[nonce].timeout = setTimeout(function() {
            onFailure({ nonce: nonce, reason: 'Operation timed out. Try again or download manually.' });
        },10000);
    };

    var requestDownload = function(e) {
        if (/working|requested/.test(e.target.className)) return; 
        e.preventDefault();
        e.target.className += ' working';
        var isTorrent = /torrentLink/.test(e.target.className);
        var gallery   = e.target.getAttribute('target');
        var nonce     = Math.floor(Math.random() * (1<<30));
        var iframe    = document.createElement('iframe');
        downloadData[nonce] = { button: e.target, iframe: iframe, timeout: null };
        generateTimeout(nonce);
        document.body.appendChild(iframe);
        iframe.className = 'EHADiframe';
        iframe.src = addMetadata(gallery,{ nonce: nonce, isTorrent: isTorrent });
    };

    window.addEventListener('load',start,false);

}