NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==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 https://exhentai.org/* // @grant GM_xmlhttpRequest // @run-at document-start // @author etc // @version 2.0.1 // @namespace https://greasyfork.org/users/2168 // ==/UserScript== /* * * * * promise-polyfill * * * * */ // https://github.com/taylorhakes/promise-polyfill // Copyright (c) 2014 Taylor Hakes // Copyright (c) 2014 Forbes Lindesay // MIT License !function(e){function n(){}function t(e,n){return function(){e.apply(n,arguments)}}function o(e){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._state=0,this._handled=!1,this._value=void 0,this._deferreds=[],s(e,this)}function i(e,n){for(;3===e._state;)e=e._value;return 0===e._state?void e._deferreds.push(n):(e._handled=!0,void o._immediateFn(function(){var t=1===e._state?n.onFulfilled:n.onRejected;if(null===t)return void(1===e._state?r:u)(n.promise,e._value);var o;try{o=t(e._value)}catch(i){return void u(n.promise,i)}r(n.promise,o)}))}function r(e,n){try{if(n===e)throw new TypeError("A promise cannot be resolved with itself.");if(n&&("object"==typeof n||"function"==typeof n)){var i=n.then;if(n instanceof o)return e._state=3,e._value=n,void f(e);if("function"==typeof i)return void s(t(i,n),e)}e._state=1,e._value=n,f(e)}catch(r){u(e,r)}}function u(e,n){e._state=2,e._value=n,f(e)}function f(e){2===e._state&&0===e._deferreds.length&&o._immediateFn(function(){e._handled||o._unhandledRejectionFn(e._value)});for(var n=0,t=e._deferreds.length;n<t;n++)i(e,e._deferreds[n]);e._deferreds=null}function c(e,n,t){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof n?n:null,this.promise=t}function s(e,n){var t=!1;try{e(function(e){t||(t=!0,r(n,e))},function(e){t||(t=!0,u(n,e))})}catch(o){if(t)return;t=!0,u(n,o)}}var a=setTimeout;o.prototype["catch"]=function(e){return this.then(null,e)},o.prototype.then=function(e,t){var o=new this.constructor(n);return i(this,new c(e,t,o)),o},o.all=function(e){var n=Array.prototype.slice.call(e);return new o(function(e,t){function o(r,u){try{if(u&&("object"==typeof u||"function"==typeof u)){var f=u.then;if("function"==typeof f)return void f.call(u,function(e){o(r,e)},t)}n[r]=u,0===--i&&e(n)}catch(c){t(c)}}if(0===n.length)return e([]);for(var i=n.length,r=0;r<n.length;r++)o(r,n[r])})},o.resolve=function(e){return e&&"object"==typeof e&&e.constructor===o?e:new o(function(n){n(e)})},o.reject=function(e){return new o(function(n,t){t(e)})},o.race=function(e){return new o(function(n,t){for(var o=0,i=e.length;o<i;o++)e[o].then(n,t)})},o._immediateFn="function"==typeof setImmediate&&function(e){setImmediate(e)}||function(e){a(e,0)},o._unhandledRejectionFn=function(e){"undefined"!=typeof console&&console&&console.warn("Possible Unhandled Promise Rejection:",e)},o._setImmediateFn=function(e){o._immediateFn=e},o._setUnhandledRejectionFn=function(e){o._unhandledRejectionFn=e},"undefined"!=typeof module&&module.exports?module.exports=o:e.Promise||(e.Promise=o)}(this); /* * * * * Resources * * * * */ 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 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=='; /* * * * * UI utilities * * * * */ 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; }; /* * * * * Utilities * * * * */ var xhr = function(data) { var request = { method: data.method, url: data.url, onload: data.callback }; if (data.headers) request.headers = data.headers; if (data.onerror) request.onerror = data.onerror; if (data.body && data.body.constructor == String) request.data = data.body; else if (data.body) request.data = JSON.stringify(data.body); GM_xmlhttpRequest(request); }; /* * * * * Download steps * * * * */ var obtainArchiverKey = function(data) { return new Promise(function(resolve, reject) { xhr({ method: 'GET', url: window.location.protocol + '//' + window.location.host + '/g/' + data.galleryId + '/' + data.galleryToken + '?random=' + Date.now(), callback: function(response) { var div = document.createElement('div'); div.innerHTML = response.responseText.replace(/src=/g, 'no-src='); var target = div.querySelector('[onclick*="archiver.php"]'); if (!target) data.error = 'could not resolve archiver key'; else { var tokens = target.getAttribute('onclick').match(/or=([^'"]+)/); if (!tokens) data.error = 'could not resolve archiver key'; else data.archiverKey = tokens[1]; } if (data.error) reject(data); else resolve(data); }, onerror: function() { data.error = 'could not open gallery\'s page'; reject(data); } }); }); }; var obtainTorrentFile = function(data) { return new Promise(function(resolve, reject) { xhr({ method: 'GET', url: window.location.protocol + '//' + window.location.host + '/gallerytorrents.php?gid=' + data.galleryId + '&t=' + data.galleryToken, callback: function(response) { var div = document.createElement('div'); div.innerHTML = response.responseText.replace(/src=/g, 'no-src='); var forms = div.querySelectorAll('form'), result = null; for (var i=0;i<forms.length;++i) { var link = forms[i].querySelector('a'); if (!link) continue; var posted = document.evaluate('.//span[contains(text(),"Posted")]', forms[i], null, 9, null).singleNodeValue; var seeds = document.evaluate('.//span[contains(text(),"Seeds")]', forms[i], null, 9, null).singleNodeValue; if (!posted || !seeds) continue; posted = new Date(posted.nextSibling.textContent.trim()); seeds = parseInt(seeds.nextSibling.textContent, 10); if (seeds === 0) continue; if (result == null || (result.date - posted < 0)) result = { date: posted, link: link.href }; } if (result === null) data.error = 'could not find any seeded torrent'; else data.fileUrl = result.link; if (data.error) reject(data); else resolve(data); }, onerror: function() { data.error = 'could not obtain torrent list'; reject(data); } }); }); }; var submitDownloadRequest = function(data) { return new Promise(function(resolve, reject) { if (!data || data.error) { resolve(data); return; } xhr({ method: 'POST', url: window.location.protocol + '//' + window.location.host + '/archiver.php?gid=' + data.galleryId + '&token=' + data.galleryToken + '&or=' + data.archiverKey.replace(/--/, '-'), headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'dltype=org&dlcheck=Download+Original+Archive', callback: function(response) { var div = document.createElement('div'), url = null; div.innerHTML = response.responseText.replace(/src=/g, 'no-src='); var target = div.querySelector('#continue > a'); if (target) url = target.href; else { var targets = div.querySelectorAll('script'); for (var i=0;i<targets.length;++i) { var match = targets[i].textContent.match(/location\s*=\s*"(.+?)"/); if (!match) continue; url = match[1]; break; } } if (url) data.archiverUrl = url; else data.error = 'could not resolve archiver URL'; if (data.error) reject(data); else resolve(data); }, onerror: function() { data.error = 'could not access archiver'; reject(data); } }); }); }; var waitForDownloadLink = function(data) { return new Promise(function(resolve, reject) { if (!data || data.error) { resolve(data); return; } xhr({ method: 'GET', url: data.archiverUrl, callback: function(response) { if (/The file was successfully prepared/i.test(response.responseText)) { var div = document.createElement('div'); div.innerHTML = response.responseText.replace(/src=/g, 'no-src='); var target = div.querySelector('#db a'); if (target) { var archiverUrl = new URL(data.archiverUrl); data.fileUrl = archiverUrl.protocol + '//' + archiverUrl.host + target.getAttribute('href'); } else data.error = 'could not resolve file URL'; } else data.error = 'archiver did not provide file URL'; if (data.error) reject(data); else resolve(data); }, onerror: function() { data.error = 'could not contact archiver'; if (/https/.test(window.location.protocol)) { data.error += '; this is most likely caused by mixed-content security policies enforced by the' + ' browser that need to be disabled by the user. If you have no clue how to do that, you' + ' should probably Google "how to disable mixed-content blocking".'; } else { data.error += '; please check whether your browser is not blocking XHR requests towards' + ' 3rd-party URLs'; } reject(data); } }); }); }; var downloadFile = function(data) { return new Promise(function(resolve, reject) { if (!data || data.error) { resolve(data); return; } var a = document.createElement('a'); a.href = data.fileUrl; document.body.appendChild(a); a.click(); document.body.removeChild(a); document.body.appendChild(a); resolve(data); }); }; var updateUI = function(data) { if (!data || data.error) return; var temp = (data.isTorrent ? torrentQueue[data.galleryId] : archiveQueue[data.galleryId]); temp.button.className = temp.button.className.replace(/\s*working/, '') + ' requested'; }; var handleFailure = function(data) { if (!data) return; var temp = (data.isTorrent ? torrentQueue[data.galleryId] : archiveQueue[data.galleryId]); temp.button.className = temp.button.className.replace(/\s*working/, ''); alert('Could not complete operation.\nReason: ' + (data.error || 'unknown')); }; /* * * * * State management * * * * */ var archiveQueue = { }, torrentQueue = { }; 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 tokens = e.target.getAttribute('target').match(/\/g\/(\d+)\/([0-9a-z]+)/i); var galleryId = parseInt(tokens[1], 10), galleryToken = tokens[2]; if (!isTorrent) { archiveQueue[galleryId] = { token: galleryToken, button: e.target }; obtainArchiverKey({ galleryId: galleryId, galleryToken: galleryToken, isTorrent: false }) .then(submitDownloadRequest, handleFailure) .then(waitForDownloadLink, handleFailure) .then(downloadFile, handleFailure) .then(updateUI, handleFailure); } else { torrentQueue[galleryId] = { token: galleryToken, button: e.target }; obtainTorrentFile({ galleryId: galleryId, galleryToken: galleryToken, isTorrent: true }) .then(downloadFile, handleFailure) .then(updateUI, handleFailure); } }; /* * * * * UI setup * * * * */ window.addEventListener('load', 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 }); } // 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); }, false);