e-hentai retriever

e-hentai & exhentai image url retriever

As of 2017-08-30. See the latest version.

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

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

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

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

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

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

(I already have a user script manager, let me install it!)

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.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        e-hentai retriever
// @namespace   http://e-hentai.org
// @description e-hentai & exhentai image url retriever
// @include     /^https?:\/\/e-hentai.org\/s\/.*/
// @include     /^https?:\/\/exhentai.org\/s\/.*/
// @version     4.0.0
// @grant       GM_xmlhttpRequest
// @grant       unsafeWindow
// ==/UserScript==

/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// identity function for calling harmony imports with the correct context
/******/ 	__webpack_require__.i = function(value) { return value; };
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, {
/******/ 				configurable: false,
/******/ 				enumerable: true,
/******/ 				get: getter
/******/ 			});
/******/ 		}
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 9);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {

// style-loader: Adds some css to the DOM by adding a <style> tag

// load the styles
var content = __webpack_require__(2);
if(typeof content === 'string') content = [[module.i, content, '']];
// Prepare cssTransformation
var transform;

var options = {}
options.transform = transform
// add the styles to the DOM
var update = __webpack_require__(5)(content, options);
if(content.locals) module.exports = content.locals;
// Hot Module Replacement
if(false) {
	// When the styles change, update the <style> tags
	if(!content.locals) {
		module.hot.accept("!!../node_modules/css-loader/index.js!./style.css", function() {
			var newContent = require("!!../node_modules/css-loader/index.js!./style.css");
			if(typeof newContent === 'string') newContent = [[module.id, newContent, '']];
			update(newContent);
		});
	}
	// When the module is disposed, remove the <style> tags
	module.hot.dispose(function() { update(); });
}

/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const queue_1 = __webpack_require__(8);
const cofetch_1 = __webpack_require__(7);
const EventEmitter = __webpack_require__(4);
class EhRetriever extends EventEmitter {
    constructor(url, html) {
        super();
        const testEXHentaiUrl = /^https?:\/\/(?:e-|ex)hentai\.org\//;
        if (typeof url !== 'string') {
            throw new TypeError('invalid `url`, expected a string');
        }
        if (!testEXHentaiUrl.test(url)) {
            throw new TypeError(`invalid url: ${url}`);
        }
        this.url = url;
        this.html = html;
        this.gallery = { gid: '', token: '' };
        this.referer = url;
        this.showkey = '';
        this.ehentaiHost = testEXHentaiUrl.exec(url)[0].slice(0, -1);
        this.q = new queue_1.default(3, 3000, 1000);
        this.pages = this.init();
        this.pages.then(() => this.emit('ready'));
    }
    init() {
        return __awaiter(this, void 0, void 0, function* () {
            if (!this.html) {
                this.html = yield this.fetch(this.url).then(res => res.text());
            }
            const galleryURL = this.html.match(/hentai\.org\/g\/(\d+)\/([a-z0-9]+)/i);
            const showkey = this.html.match(/showkey="([^"]+)"/i);
            if (galleryURL) {
                this.gallery.gid = galleryURL[1];
                this.gallery.token = galleryURL[2];
            }
            else {
                throw new Error("Can't get gallery URL");
            }
            if (showkey) {
                this.showkey = showkey[1];
            }
            else {
                throw new Error("Can't get showkey");
            }
            return yield this.getAllPageURL();
        });
    }
    getAllPageURL() {
        return __awaiter(this, void 0, void 0, function* () {
            const { ehentaiHost, gallery: { gid, token } } = this;
            const firstPage = yield this.fetch(`${ehentaiHost}/g/${gid}/${token}`).then(res => res.text());
            let pageNum;
            const pageLinksTable = firstPage.match(/<table[^>]*class="ptt"[^>]*>((?:[^<]*)(?:<(?!\/table>)[^<]*)*)<\/table>/);
            if (pageLinksTable) {
                const pageLinks = pageLinksTable[1].match(/g\/[^/]+\/[^/]+\/\?p=\d+/g);
                if (pageLinks) {
                    pageNum = Math.max.apply(null, pageLinks.map(e => parseInt(/\d+$/.exec(e)[0], 10)));
                }
                else {
                    pageNum = 0;
                }
            }
            else {
                throw new Error('Cant get page numbers');
            }
            const allPages = yield Promise.all(Array(pageNum).fill(undefined).map((e, i) => {
                return this.fetch(`${ehentaiHost}/g/${gid}/${token}/?p=${i + 1}`).then(res => res.text());
            }));
            allPages.unshift(firstPage);
            return allPages
                .map(e => e.match(/<div[^>]*class="gdt\w"[^>]*>(?:(?:[^<]*)(?:<(?!\/div>)[^<]*)*)<\/div>/g))
                .reduce((p, c) => p.concat(c)) // 2d array to 1d
                .map(e => {
                const [, imgkey, page] = e.match(/s\/(\w+)\/\d+-(\d+)/);
                return { imgkey, page: parseInt(page, 10) };
            });
        });
    }
    fetch(url, options = {}) {
        if (typeof url !== 'string') {
            return Promise.reject(new TypeError('invalid `url`, expected a string'));
        }
        if (url.search(/^https?:\/\//) < 0) {
            return Promise.reject(new TypeError(`invalid url: ${url}`));
        }
        const cofetchOptions = {
            method: 'GET',
            credentials: 'include',
            headers: {
                'User-Agent': navigator.userAgent,
                Referer: this.referer
            }
        };
        for (const key of Object.keys(options)) {
            if (key === 'headers') {
                Object.assign(cofetchOptions.headers, options.headers);
            }
            else {
                cofetchOptions[key] = options[key];
            }
        }
        return this.q.queue((resolve, reject) => {
            cofetch_1.default(url, cofetchOptions).then(resolve).catch(reject);
        }, `Fetch ${url} ${JSON.stringify(cofetchOptions)}`);
    }
    retrieve(start = 0, stop = -1) {
        return __awaiter(this, void 0, void 0, function* () {
            const pages = yield this.pages;
            if (start < 0 || start >= pages.length || isNaN(start)) {
                throw new RangeError(`invalid start number: ${start}`);
            }
            if (stop < 0) {
                stop = pages.length - 1;
            }
            else if (stop < start || stop >= pages.length || isNaN(stop)) {
                throw new RangeError(`invalid stop number: ${stop}, start: ${start}`);
            }
            const retrievePages = pages.slice(start, stop + 1);
            const loadPage = (e) => __awaiter(this, void 0, void 0, function* () {
                if (e.imgsrc && e.filename) {
                    return Promise.resolve(e);
                }
                const fetchPage = yield this.fetch(`${this.ehentaiHost}/api.php`, {
                    method: 'POST',
                    headers: { 'Content-Type': 'application/json' },
                    // assign e = {'imgkey': ..., 'page': ...} to object literal {'method': ..., 'gid': ..., 'showkey': ...}
                    // does not modify e
                    body: JSON.stringify(Object.assign({
                        method: 'showpage',
                        gid: this.gallery.gid,
                        showkey: this.showkey
                    }, e))
                }).then(res => res.json());
                this.emit('load', {
                    current: e.page - start,
                    total: stop - start + 1
                });
                return fetchPage;
            });
            const imagePages = yield Promise.all(retrievePages.map(loadPage));
            imagePages.forEach((e, i) => {
                retrievePages[i].filename = e.i.match(/>([^:]+):/)[1].trim();
                retrievePages[i].imgsrc = e.i3.match(/src="([^"]+)"/)[1];
                retrievePages[i].failnl = new Set([e.i6.match(/nl\('([^']+)'/)[1]]);
                retrievePages[i].style = e.i3.match(/style="([^"]+)"/)[1];
                retrievePages[i].url = e.s;
            });
            return retrievePages;
        });
    }
    fail(index) {
        return __awaiter(this, void 0, void 0, function* () {
            const { ehentaiHost } = this;
            const pages = yield this.pages;
            const failPage = pages[index - 1];
            const failnl = [...failPage.failnl.values()].map(e => `nl=${e}`).join('&');
            const res = yield this.fetch(`${this.ehentaiHost}/${failPage.url}?${failnl}`).then(res => res.text());
            const parsed = res.match(/<img[^>]*id="img"[^>]*src="([^"]+)"[^>]*.*onclick="return nl\('([0-9-]+)'\)/i);
            if (parsed) {
                failPage.imgsrc = parsed[1];
                failPage.failnl.add(parsed[2]);
                return failPage;
            }
            return null;
        });
    }
}
exports.EhRetriever = EhRetriever;
exports.default = EhRetriever;


/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {

exports = module.exports = __webpack_require__(3)(undefined);
// imports


// module
exports.push([module.i, "#i3 a{display:inline-block;position:relative}.close,.page-number,.swap{position:absolute;width:32px;height:32px;margin:8px;z-index:999;opacity:0;transition:opacity .25s;background-color:hsla(0,0%,100%,.3)}.close{top:0;right:0;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAmElEQVRYhc2USQ6AMAwD+/9PhyuRAs3igVrqCTR2m2UtL1u8Hj3sdkjz0MOCQ5o7j+iDOsTWgwyRZhMhykxliDZLEWLMmABkr9gByfuoAsQmKQPGd8mbAW7eDYHoV/NsCFxH3/6I+h8xAZ/tgMo/mDkWogOUhZiAxiEUt2gzlHUss4hOTjPJWd6y8UXSDaFWqQyUUo1Iy3lcN6p2mB7qGCwAAAAASUVORK5CYII=)}.swap{top:0;left:0;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAdklEQVRYhe3TwQ0AIQhE0enA7b9JS2BPezExERb9HpiEI/KiIlUqZ2I3AFCECUaYYMQI+IXokwMjhQNCCPwGmqTHWal/IJKrtsDVlA3Y17Bwnns4/lb4TzX5161lDo9UJ4eHAZmIMGCGOB4cMCKw4IAPUalszwvTfbCPlftI4QAAAABJRU5ErkJggg==)}.page-number{bottom:0;right:0;font-size:16px;line-height:32px;color:#000}.close:hover,.page-number:hover,.swap:hover{opacity:1}.hidden{display:none!important}.show-hidden{font-size:larger;margin-left:5px}", ""]);

// exports


/***/ }),
/* 3 */
/***/ (function(module, exports) {

/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/
// css base code, injected by the css-loader
module.exports = function(useSourceMap) {
	var list = [];

	// return the list of modules as css string
	list.toString = function toString() {
		return this.map(function (item) {
			var content = cssWithMappingToString(item, useSourceMap);
			if(item[2]) {
				return "@media " + item[2] + "{" + content + "}";
			} else {
				return content;
			}
		}).join("");
	};

	// import a list of modules into the list
	list.i = function(modules, mediaQuery) {
		if(typeof modules === "string")
			modules = [[null, modules, ""]];
		var alreadyImportedModules = {};
		for(var i = 0; i < this.length; i++) {
			var id = this[i][0];
			if(typeof id === "number")
				alreadyImportedModules[id] = true;
		}
		for(i = 0; i < modules.length; i++) {
			var item = modules[i];
			// skip already imported module
			// this implementation is not 100% perfect for weird media query combinations
			//  when a module is imported multiple times with different media queries.
			//  I hope this will never occur (Hey this way we have smaller bundles)
			if(typeof item[0] !== "number" || !alreadyImportedModules[item[0]]) {
				if(mediaQuery && !item[2]) {
					item[2] = mediaQuery;
				} else if(mediaQuery) {
					item[2] = "(" + item[2] + ") and (" + mediaQuery + ")";
				}
				list.push(item);
			}
		}
	};
	return list;
};

function cssWithMappingToString(item, useSourceMap) {
	var content = item[1] || '';
	var cssMapping = item[3];
	if (!cssMapping) {
		return content;
	}

	if (useSourceMap && typeof btoa === 'function') {
		var sourceMapping = toComment(cssMapping);
		var sourceURLs = cssMapping.sources.map(function (source) {
			return '/*# sourceURL=' + cssMapping.sourceRoot + source + ' */'
		});

		return [content].concat(sourceURLs).concat([sourceMapping]).join('\n');
	}

	return [content].join('\n');
}

// Adapted from convert-source-map (MIT)
function toComment(sourceMap) {
	// eslint-disable-next-line no-undef
	var base64 = btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap))));
	var data = 'sourceMappingURL=data:application/json;charset=utf-8;base64,' + base64;

	return '/*# ' + data + ' */';
}


/***/ }),
/* 4 */
/***/ (function(module, exports) {

// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.

function EventEmitter() {
  this._events = this._events || {};
  this._maxListeners = this._maxListeners || undefined;
}
module.exports = EventEmitter;

// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;

EventEmitter.prototype._events = undefined;
EventEmitter.prototype._maxListeners = undefined;

// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
EventEmitter.defaultMaxListeners = 10;

// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.setMaxListeners = function(n) {
  if (!isNumber(n) || n < 0 || isNaN(n))
    throw TypeError('n must be a positive number');
  this._maxListeners = n;
  return this;
};

EventEmitter.prototype.emit = function(type) {
  var er, handler, len, args, i, listeners;

  if (!this._events)
    this._events = {};

  // If there is no 'error' event listener then throw.
  if (type === 'error') {
    if (!this._events.error ||
        (isObject(this._events.error) && !this._events.error.length)) {
      er = arguments[1];
      if (er instanceof Error) {
        throw er; // Unhandled 'error' event
      } else {
        // At least give some kind of context to the user
        var err = new Error('Uncaught, unspecified "error" event. (' + er + ')');
        err.context = er;
        throw err;
      }
    }
  }

  handler = this._events[type];

  if (isUndefined(handler))
    return false;

  if (isFunction(handler)) {
    switch (arguments.length) {
      // fast cases
      case 1:
        handler.call(this);
        break;
      case 2:
        handler.call(this, arguments[1]);
        break;
      case 3:
        handler.call(this, arguments[1], arguments[2]);
        break;
      // slower
      default:
        args = Array.prototype.slice.call(arguments, 1);
        handler.apply(this, args);
    }
  } else if (isObject(handler)) {
    args = Array.prototype.slice.call(arguments, 1);
    listeners = handler.slice();
    len = listeners.length;
    for (i = 0; i < len; i++)
      listeners[i].apply(this, args);
  }

  return true;
};

EventEmitter.prototype.addListener = function(type, listener) {
  var m;

  if (!isFunction(listener))
    throw TypeError('listener must be a function');

  if (!this._events)
    this._events = {};

  // To avoid recursion in the case that type === "newListener"! Before
  // adding it to the listeners, first emit "newListener".
  if (this._events.newListener)
    this.emit('newListener', type,
              isFunction(listener.listener) ?
              listener.listener : listener);

  if (!this._events[type])
    // Optimize the case of one listener. Don't need the extra array object.
    this._events[type] = listener;
  else if (isObject(this._events[type]))
    // If we've already got an array, just append.
    this._events[type].push(listener);
  else
    // Adding the second element, need to change to array.
    this._events[type] = [this._events[type], listener];

  // Check for listener leak
  if (isObject(this._events[type]) && !this._events[type].warned) {
    if (!isUndefined(this._maxListeners)) {
      m = this._maxListeners;
    } else {
      m = EventEmitter.defaultMaxListeners;
    }

    if (m && m > 0 && this._events[type].length > m) {
      this._events[type].warned = true;
      console.error('(node) warning: possible EventEmitter memory ' +
                    'leak detected. %d listeners added. ' +
                    'Use emitter.setMaxListeners() to increase limit.',
                    this._events[type].length);
      if (typeof console.trace === 'function') {
        // not supported in IE 10
        console.trace();
      }
    }
  }

  return this;
};

EventEmitter.prototype.on = EventEmitter.prototype.addListener;

EventEmitter.prototype.once = function(type, listener) {
  if (!isFunction(listener))
    throw TypeError('listener must be a function');

  var fired = false;

  function g() {
    this.removeListener(type, g);

    if (!fired) {
      fired = true;
      listener.apply(this, arguments);
    }
  }

  g.listener = listener;
  this.on(type, g);

  return this;
};

// emits a 'removeListener' event iff the listener was removed
EventEmitter.prototype.removeListener = function(type, listener) {
  var list, position, length, i;

  if (!isFunction(listener))
    throw TypeError('listener must be a function');

  if (!this._events || !this._events[type])
    return this;

  list = this._events[type];
  length = list.length;
  position = -1;

  if (list === listener ||
      (isFunction(list.listener) && list.listener === listener)) {
    delete this._events[type];
    if (this._events.removeListener)
      this.emit('removeListener', type, listener);

  } else if (isObject(list)) {
    for (i = length; i-- > 0;) {
      if (list[i] === listener ||
          (list[i].listener && list[i].listener === listener)) {
        position = i;
        break;
      }
    }

    if (position < 0)
      return this;

    if (list.length === 1) {
      list.length = 0;
      delete this._events[type];
    } else {
      list.splice(position, 1);
    }

    if (this._events.removeListener)
      this.emit('removeListener', type, listener);
  }

  return this;
};

EventEmitter.prototype.removeAllListeners = function(type) {
  var key, listeners;

  if (!this._events)
    return this;

  // not listening for removeListener, no need to emit
  if (!this._events.removeListener) {
    if (arguments.length === 0)
      this._events = {};
    else if (this._events[type])
      delete this._events[type];
    return this;
  }

  // emit removeListener for all listeners on all events
  if (arguments.length === 0) {
    for (key in this._events) {
      if (key === 'removeListener') continue;
      this.removeAllListeners(key);
    }
    this.removeAllListeners('removeListener');
    this._events = {};
    return this;
  }

  listeners = this._events[type];

  if (isFunction(listeners)) {
    this.removeListener(type, listeners);
  } else if (listeners) {
    // LIFO order
    while (listeners.length)
      this.removeListener(type, listeners[listeners.length - 1]);
  }
  delete this._events[type];

  return this;
};

EventEmitter.prototype.listeners = function(type) {
  var ret;
  if (!this._events || !this._events[type])
    ret = [];
  else if (isFunction(this._events[type]))
    ret = [this._events[type]];
  else
    ret = this._events[type].slice();
  return ret;
};

EventEmitter.prototype.listenerCount = function(type) {
  if (this._events) {
    var evlistener = this._events[type];

    if (isFunction(evlistener))
      return 1;
    else if (evlistener)
      return evlistener.length;
  }
  return 0;
};

EventEmitter.listenerCount = function(emitter, type) {
  return emitter.listenerCount(type);
};

function isFunction(arg) {
  return typeof arg === 'function';
}

function isNumber(arg) {
  return typeof arg === 'number';
}

function isObject(arg) {
  return typeof arg === 'object' && arg !== null;
}

function isUndefined(arg) {
  return arg === void 0;
}


/***/ }),
/* 5 */
/***/ (function(module, exports, __webpack_require__) {

/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

var stylesInDom = {};

var	memoize = function (fn) {
	var memo;

	return function () {
		if (typeof memo === "undefined") memo = fn.apply(this, arguments);
		return memo;
	};
};

var isOldIE = memoize(function () {
	// Test for IE <= 9 as proposed by Browserhacks
	// @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805
	// Tests for existence of standard globals is to allow style-loader
	// to operate correctly into non-standard environments
	// @see https://github.com/webpack-contrib/style-loader/issues/177
	return window && document && document.all && !window.atob;
});

var getElement = (function (fn) {
	var memo = {};

	return function(selector) {
		if (typeof memo[selector] === "undefined") {
			memo[selector] = fn.call(this, selector);
		}

		return memo[selector]
	};
})(function (target) {
	return document.querySelector(target)
});

var singleton = null;
var	singletonCounter = 0;
var	stylesInsertedAtTop = [];

var	fixUrls = __webpack_require__(6);

module.exports = function(list, options) {
	if (typeof DEBUG !== "undefined" && DEBUG) {
		if (typeof document !== "object") throw new Error("The style-loader cannot be used in a non-browser environment");
	}

	options = options || {};

	options.attrs = typeof options.attrs === "object" ? options.attrs : {};

	// Force single-tag solution on IE6-9, which has a hard limit on the # of <style>
	// tags it will allow on a page
	if (!options.singleton) options.singleton = isOldIE();

	// By default, add <style> tags to the <head> element
	if (!options.insertInto) options.insertInto = "head";

	// By default, add <style> tags to the bottom of the target
	if (!options.insertAt) options.insertAt = "bottom";

	var styles = listToStyles(list, options);

	addStylesToDom(styles, options);

	return function update (newList) {
		var mayRemove = [];

		for (var i = 0; i < styles.length; i++) {
			var item = styles[i];
			var domStyle = stylesInDom[item.id];

			domStyle.refs--;
			mayRemove.push(domStyle);
		}

		if(newList) {
			var newStyles = listToStyles(newList, options);
			addStylesToDom(newStyles, options);
		}

		for (var i = 0; i < mayRemove.length; i++) {
			var domStyle = mayRemove[i];

			if(domStyle.refs === 0) {
				for (var j = 0; j < domStyle.parts.length; j++) domStyle.parts[j]();

				delete stylesInDom[domStyle.id];
			}
		}
	};
};

function addStylesToDom (styles, options) {
	for (var i = 0; i < styles.length; i++) {
		var item = styles[i];
		var domStyle = stylesInDom[item.id];

		if(domStyle) {
			domStyle.refs++;

			for(var j = 0; j < domStyle.parts.length; j++) {
				domStyle.parts[j](item.parts[j]);
			}

			for(; j < item.parts.length; j++) {
				domStyle.parts.push(addStyle(item.parts[j], options));
			}
		} else {
			var parts = [];

			for(var j = 0; j < item.parts.length; j++) {
				parts.push(addStyle(item.parts[j], options));
			}

			stylesInDom[item.id] = {id: item.id, refs: 1, parts: parts};
		}
	}
}

function listToStyles (list, options) {
	var styles = [];
	var newStyles = {};

	for (var i = 0; i < list.length; i++) {
		var item = list[i];
		var id = options.base ? item[0] + options.base : item[0];
		var css = item[1];
		var media = item[2];
		var sourceMap = item[3];
		var part = {css: css, media: media, sourceMap: sourceMap};

		if(!newStyles[id]) styles.push(newStyles[id] = {id: id, parts: [part]});
		else newStyles[id].parts.push(part);
	}

	return styles;
}

function insertStyleElement (options, style) {
	var target = getElement(options.insertInto)

	if (!target) {
		throw new Error("Couldn't find a style target. This probably means that the value for the 'insertInto' parameter is invalid.");
	}

	var lastStyleElementInsertedAtTop = stylesInsertedAtTop[stylesInsertedAtTop.length - 1];

	if (options.insertAt === "top") {
		if (!lastStyleElementInsertedAtTop) {
			target.insertBefore(style, target.firstChild);
		} else if (lastStyleElementInsertedAtTop.nextSibling) {
			target.insertBefore(style, lastStyleElementInsertedAtTop.nextSibling);
		} else {
			target.appendChild(style);
		}
		stylesInsertedAtTop.push(style);
	} else if (options.insertAt === "bottom") {
		target.appendChild(style);
	} else {
		throw new Error("Invalid value for parameter 'insertAt'. Must be 'top' or 'bottom'.");
	}
}

function removeStyleElement (style) {
	if (style.parentNode === null) return false;
	style.parentNode.removeChild(style);

	var idx = stylesInsertedAtTop.indexOf(style);
	if(idx >= 0) {
		stylesInsertedAtTop.splice(idx, 1);
	}
}

function createStyleElement (options) {
	var style = document.createElement("style");

	options.attrs.type = "text/css";

	addAttrs(style, options.attrs);
	insertStyleElement(options, style);

	return style;
}

function createLinkElement (options) {
	var link = document.createElement("link");

	options.attrs.type = "text/css";
	options.attrs.rel = "stylesheet";

	addAttrs(link, options.attrs);
	insertStyleElement(options, link);

	return link;
}

function addAttrs (el, attrs) {
	Object.keys(attrs).forEach(function (key) {
		el.setAttribute(key, attrs[key]);
	});
}

function addStyle (obj, options) {
	var style, update, remove, result;

	// If a transform function was defined, run it on the css
	if (options.transform && obj.css) {
	    result = options.transform(obj.css);

	    if (result) {
	    	// If transform returns a value, use that instead of the original css.
	    	// This allows running runtime transformations on the css.
	    	obj.css = result;
	    } else {
	    	// If the transform function returns a falsy value, don't add this css.
	    	// This allows conditional loading of css
	    	return function() {
	    		// noop
	    	};
	    }
	}

	if (options.singleton) {
		var styleIndex = singletonCounter++;

		style = singleton || (singleton = createStyleElement(options));

		update = applyToSingletonTag.bind(null, style, styleIndex, false);
		remove = applyToSingletonTag.bind(null, style, styleIndex, true);

	} else if (
		obj.sourceMap &&
		typeof URL === "function" &&
		typeof URL.createObjectURL === "function" &&
		typeof URL.revokeObjectURL === "function" &&
		typeof Blob === "function" &&
		typeof btoa === "function"
	) {
		style = createLinkElement(options);
		update = updateLink.bind(null, style, options);
		remove = function () {
			removeStyleElement(style);

			if(style.href) URL.revokeObjectURL(style.href);
		};
	} else {
		style = createStyleElement(options);
		update = applyToTag.bind(null, style);
		remove = function () {
			removeStyleElement(style);
		};
	}

	update(obj);

	return function updateStyle (newObj) {
		if (newObj) {
			if (
				newObj.css === obj.css &&
				newObj.media === obj.media &&
				newObj.sourceMap === obj.sourceMap
			) {
				return;
			}

			update(obj = newObj);
		} else {
			remove();
		}
	};
}

var replaceText = (function () {
	var textStore = [];

	return function (index, replacement) {
		textStore[index] = replacement;

		return textStore.filter(Boolean).join('\n');
	};
})();

function applyToSingletonTag (style, index, remove, obj) {
	var css = remove ? "" : obj.css;

	if (style.styleSheet) {
		style.styleSheet.cssText = replaceText(index, css);
	} else {
		var cssNode = document.createTextNode(css);
		var childNodes = style.childNodes;

		if (childNodes[index]) style.removeChild(childNodes[index]);

		if (childNodes.length) {
			style.insertBefore(cssNode, childNodes[index]);
		} else {
			style.appendChild(cssNode);
		}
	}
}

function applyToTag (style, obj) {
	var css = obj.css;
	var media = obj.media;

	if(media) {
		style.setAttribute("media", media)
	}

	if(style.styleSheet) {
		style.styleSheet.cssText = css;
	} else {
		while(style.firstChild) {
			style.removeChild(style.firstChild);
		}

		style.appendChild(document.createTextNode(css));
	}
}

function updateLink (link, options, obj) {
	var css = obj.css;
	var sourceMap = obj.sourceMap;

	/*
		If convertToAbsoluteUrls isn't defined, but sourcemaps are enabled
		and there is no publicPath defined then lets turn convertToAbsoluteUrls
		on by default.  Otherwise default to the convertToAbsoluteUrls option
		directly
	*/
	var autoFixUrls = options.convertToAbsoluteUrls === undefined && sourceMap;

	if (options.convertToAbsoluteUrls || autoFixUrls) {
		css = fixUrls(css);
	}

	if (sourceMap) {
		// http://stackoverflow.com/a/26603875
		css += "\n/*# sourceMappingURL=data:application/json;base64," + btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))) + " */";
	}

	var blob = new Blob([css], { type: "text/css" });

	var oldSrc = link.href;

	link.href = URL.createObjectURL(blob);

	if(oldSrc) URL.revokeObjectURL(oldSrc);
}


/***/ }),
/* 6 */
/***/ (function(module, exports) {


/**
 * When source maps are enabled, `style-loader` uses a link element with a data-uri to
 * embed the css on the page. This breaks all relative urls because now they are relative to a
 * bundle instead of the current page.
 *
 * One solution is to only use full urls, but that may be impossible.
 *
 * Instead, this function "fixes" the relative urls to be absolute according to the current page location.
 *
 * A rudimentary test suite is located at `test/fixUrls.js` and can be run via the `npm test` command.
 *
 */

module.exports = function (css) {
  // get current location
  var location = typeof window !== "undefined" && window.location;

  if (!location) {
    throw new Error("fixUrls requires window.location");
  }

	// blank or null?
	if (!css || typeof css !== "string") {
	  return css;
  }

  var baseUrl = location.protocol + "//" + location.host;
  var currentDir = baseUrl + location.pathname.replace(/\/[^\/]*$/, "/");

	// convert each url(...)
	/*
	This regular expression is just a way to recursively match brackets within
	a string.

	 /url\s*\(  = Match on the word "url" with any whitespace after it and then a parens
	   (  = Start a capturing group
	     (?:  = Start a non-capturing group
	         [^)(]  = Match anything that isn't a parentheses
	         |  = OR
	         \(  = Match a start parentheses
	             (?:  = Start another non-capturing groups
	                 [^)(]+  = Match anything that isn't a parentheses
	                 |  = OR
	                 \(  = Match a start parentheses
	                     [^)(]*  = Match anything that isn't a parentheses
	                 \)  = Match a end parentheses
	             )  = End Group
              *\) = Match anything and then a close parens
          )  = Close non-capturing group
          *  = Match anything
       )  = Close capturing group
	 \)  = Match a close parens

	 /gi  = Get all matches, not the first.  Be case insensitive.
	 */
	var fixedCss = css.replace(/url\s*\(((?:[^)(]|\((?:[^)(]+|\([^)(]*\))*\))*)\)/gi, function(fullMatch, origUrl) {
		// strip quotes (if they exist)
		var unquotedOrigUrl = origUrl
			.trim()
			.replace(/^"(.*)"$/, function(o, $1){ return $1; })
			.replace(/^'(.*)'$/, function(o, $1){ return $1; });

		// already a full url? no change
		if (/^(#|data:|http:\/\/|https:\/\/|file:\/\/\/)/i.test(unquotedOrigUrl)) {
		  return fullMatch;
		}

		// convert the url to a full url
		var newUrl;

		if (unquotedOrigUrl.indexOf("//") === 0) {
		  	//TODO: should we add protocol?
			newUrl = unquotedOrigUrl;
		} else if (unquotedOrigUrl.indexOf("/") === 0) {
			// path should be relative to the base url
			newUrl = baseUrl + unquotedOrigUrl; // already starts with '/'
		} else {
			// path should be relative to current directory
			newUrl = currentDir + unquotedOrigUrl.replace(/^\.\//, ""); // Strip leading './'
		}

		// send back the fixed url(...)
		return "url(" + JSON.stringify(newUrl) + ")";
	});

	// send back the fixed css
	return fixedCss;
};


/***/ }),
/* 7 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

// Origin from window.fetch polyfill
// https://github.com/github/fetch
// License https://github.com/github/fetch/blob/master/LICENSE
Object.defineProperty(exports, "__esModule", { value: true });
exports.COFetch = (input, init = {}) => {
    let request;
    if (Request.prototype.isPrototypeOf(input) && !init) {
        request = input;
    }
    else {
        request = new Request(input, init);
    }
    const headers = Object.assign({}, request.headers);
    if (request.credentials === 'include') {
        headers['Cookie'] = document.cookie;
    }
    const onload = (resolve, reject, gmxhr) => {
        const init = {
            url: gmxhr.finalUrl || request.url,
            status: gmxhr.status,
            statusText: gmxhr.statusText,
            headers: undefined
        };
        try {
            const rawHeaders = gmxhr.responseHeaders.trim().replace(/\r\n(\s+)/g, '$1').split('\r\n').map(e => e.split(/:/));
            const header = new Headers();
            rawHeaders.forEach(e => {
                header.append(e[0].trim(), e[1].trim());
            });
            init.headers = header;
            const res = new Response(gmxhr.response, init);
            resolve(res);
        }
        catch (e) {
            reject(e);
        }
    };
    const onerror = (resolve, reject, gmxhr) => {
        reject(new TypeError('Network request failed'));
    };
    return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
            method: request.method,
            url: request.url,
            headers: headers,
            responseType: 'blob',
            data: init.body,
            onload: onload.bind(null, resolve, reject),
            onerror: onerror.bind(null, resolve, reject)
        });
    });
};
exports.default = exports.COFetch;


/***/ }),
/* 8 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", { value: true });
class Queue {
    constructor(limit, timeout = 0, delay = 0) {
        this.limit = limit;
        this.timeout = timeout;
        this.delay = delay;
        this.slot = [];
        this.q = [];
    }
    queue(executor, name) {
        const job = new Promise((resolve, reject) => {
            this.q.push({
                name,
                run: executor,
                resolve,
                reject,
                isTimeout: false,
                timeoutId: undefined
            });
        });
        console.log(`queue: job ${name} queued`);
        this.dequeue();
        return job;
    }
    dequeue() {
        const { q, slot, limit, timeout, delay } = this;
        if (slot.length < limit && q.length >= 1) {
            const job = q.shift();
            slot.push(job);
            console.log(`queue: job ${job.name} started`);
            if (timeout) {
                job.timeoutId = window.setTimeout(() => this.jobTimeout(job), timeout);
            }
            const onFulfilled = (data) => {
                if (job.isTimeout) {
                    return;
                }
                this.removeJob(job);
                window.setTimeout(() => this.dequeue(), delay); // force dequeue() run after current dequeue()
                if (job.timeoutId) {
                    window.clearTimeout(job.timeoutId);
                }
                console.log(`queue: job ${job.name} resolved`);
                job.resolve(data);
            };
            const onRejected = (reason) => {
                if (job.isTimeout) {
                    return;
                }
                this.removeJob(job);
                setTimeout(() => this.dequeue(), delay);
                if (job.timeoutId) {
                    window.clearTimeout(job.timeoutId);
                }
                console.log(`queue: job ${job.name} rejected`);
                job.reject(reason);
            };
            job.run(onFulfilled, onRejected);
        }
    }
    jobTimeout(job) {
        this.removeJob(job);
        console.log(`queue: job ${job.name} timeout`);
        job.reject(new Error(`queue: job ${job.name} timeout`));
        job = null;
    }
    removeJob(job) {
        let index = this.slot.indexOf(job);
        if (index >= 0) {
            this.slot.splice(index, 1);
            return;
        }
        index = this.q.indexOf(job);
        if (index >= 0) {
            this.q.splice(index, 1);
        }
    }
}
exports.Queue = Queue;
exports.default = Queue;


/***/ }),
/* 9 */
/***/ (function(module, exports, __webpack_require__) {

"use strict";

Object.defineProperty(exports, "__esModule", { value: true });
__webpack_require__(0);
const ehretriever_1 = __webpack_require__(1);
const LoadTimeout = 10000;
const AutoReload = true;
// helper functions
const $ = (selector) => document.querySelector(selector);
const $$ = (selector) => Array.from(document.querySelectorAll(selector));
const buttonsFragment = document.createDocumentFragment();
const buttonDoubleFrame = document.createElement('button');
const buttonRetrieve = document.createElement('button');
const buttonRange = document.createElement('button');
buttonsFragment.appendChild(buttonDoubleFrame);
buttonsFragment.appendChild(buttonRetrieve);
buttonsFragment.appendChild(buttonRange);
buttonDoubleFrame.textContent = 'Double Frame';
buttonRetrieve.textContent = 'Retrieve!';
buttonRange.textContent = 'Set Range';
$('#i1').insertBefore(buttonsFragment, $('#i2'));
let ehentaiResize;
let maxImageWidth;
let originalWidth;
let ehr;
let showHiddenImageLink = false;
const reload = (event) => {
    event.stopPropagation();
    event.preventDefault();
    const target = event.target;
    if (target.dataset.locked === 'true') {
        return;
    }
    target.dataset.locked = 'true';
    ehr.fail(parseInt(target.dataset.page, 10)).then(imgInfo => {
        target.src = imgInfo.imgsrc;
        target.parentElement.href = imgInfo.imgsrc;
        target.dataset.locked = 'false';
    });
};
const showImage = (event) => {
    event.stopPropagation();
    event.preventDefault();
    $$('#i3 a').forEach(e => {
        e.classList.remove('hidden');
    });
    event.target.remove();
    showHiddenImageLink = false;
};
const hideImage = (event) => {
    event.stopPropagation();
    event.preventDefault();
    event.target.parentElement.classList.add('hidden');
    if (!showHiddenImageLink) {
        const showHiddenImage = document.createElement('a');
        showHiddenImage.href = '';
        showHiddenImage.textContent = 'show hidden image';
        showHiddenImage.classList.add('show-hidden');
        showHiddenImage.addEventListener('click', showImage);
        buttonRetrieve.insertAdjacentElement('afterend', showHiddenImage);
        showHiddenImageLink = true;
    }
};
const swapImage = (event) => {
    event.stopPropagation();
    event.preventDefault();
    const right = event.target.parentElement;
    const left = right.previousElementSibling;
    if (left) {
        left.parentElement.insertBefore(right, left);
    }
};
buttonDoubleFrame.addEventListener('click', event => {
    if (!ehentaiResize) {
        try {
            ehentaiResize = unsafeWindow.onresize;
        }
        catch (e) {
            console.log(e);
        }
    }
    if (!maxImageWidth) {
        maxImageWidth = Math.max.apply(null, $$('#i3 a img').map(e => parseInt(e.style.width, 10)));
    }
    if (!originalWidth) {
        originalWidth = parseInt($('#i1').style.width, 10);
    }
    if (buttonDoubleFrame.textContent === 'Double Frame') {
        buttonDoubleFrame.textContent = 'Reset Frame';
        try {
            unsafeWindow.onresize = null;
        }
        catch (e) {
            console.log(e);
        }
        ;
        $('#i1').style.maxWidth = (maxImageWidth * 2 + 20) + 'px';
        $('#i1').style.width = (maxImageWidth * 2 + 20) + 'px';
    }
    else {
        buttonDoubleFrame.textContent = 'Double Frame';
        try {
            unsafeWindow.onresize = ehentaiResize;
            ehentaiResize();
        }
        catch (e) {
            console.log(e);
            $('#i1').style.maxWidth = originalWidth + 'px';
            $('#i1').style.width = originalWidth + 'px';
        }
    }
});
buttonRetrieve.addEventListener('click', event => {
    buttonRetrieve.setAttribute('disabled', '');
    buttonRange.setAttribute('disabled', '');
    buttonRetrieve.textContent = 'Initializing...';
    if (!ehr) {
        ehr = new ehretriever_1.EhRetriever(location.href, document.body.innerHTML);
        console.log(ehr);
    }
    ehr.on('ready', () => {
        buttonRetrieve.textContent = `Ready to retrieve`;
    });
    ehr.on('load', (progress) => {
        buttonRetrieve.textContent = `Retrieving ${progress.current}/${progress.total}`;
    });
    let retrieve;
    if ($('#ehrstart')) {
        const start = parseInt($('#ehrstart').value, 10);
        const stop = parseInt($('#ehrstop').value, 10);
        const pageNumMax = parseInt($('div.sn').textContent.match(/\/\s*(\d+)/)[1], 10);
        if (stop < start || start <= 0 || start > pageNumMax || stop > pageNumMax) {
            window.alert(`invalid range: ${start} - ${stop}, accepted range: 1 - ${pageNumMax}`);
            buttonRetrieve.textContent = 'Retrieve!';
            buttonRetrieve.removeAttribute('disabled');
            return;
        }
        retrieve = ehr.retrieve(start - 1, stop - 1);
        $('#ehrsetrange').remove();
    }
    else {
        retrieve = ehr.retrieve();
        buttonRange.remove();
    }
    retrieve.then(pages => {
        $('#i3 a').remove();
        const template = document.createElement('template');
        template.innerHTML = pages
            .map(e => `
        <a href="${e.imgsrc}">
          <img src="${e.imgsrc}" style="${e.style}" data-page="${e.page}" data-locked="false" />
          <div class="close"></div>
          <div class="swap"></div>
          <div class="page-number">${e.page}</div>
        </a>`)
            .join('');
        template.content.querySelectorAll('img').forEach(e => {
            e.addEventListener('error', function onError(event) {
                e.removeEventListener('error', onError);
                reload(event);
            });
            let timeout;
            if (AutoReload) {
                timeout = window.setTimeout(() => {
                    console.log(`timeout: page ${e.dataset.page}`);
                    const clickEvent = new MouseEvent('click', {
                        bubbles: true,
                        cancelable: true,
                        view: window
                    });
                    e.dispatchEvent(clickEvent);
                }, LoadTimeout);
                e.addEventListener('load', function onload() {
                    e.removeEventListener('load', onload);
                    clearTimeout(timeout);
                });
            }
        });
        $('#i3').appendChild(template.content);
        $('#i3').addEventListener('click', event => {
            if (event.target.nodeName === 'IMG') {
                reload(event);
            }
            else if (event.target.classList.contains('close')) {
                hideImage(event);
            }
            else if (event.target.classList.contains('swap')) {
                swapImage(event);
            }
            else if (event.target.classList.contains('page-number')) {
                event.preventDefault();
                event.stopPropagation();
            }
        });
        buttonRetrieve.textContent = 'Done!';
        buttonDoubleFrame.removeAttribute('disabled');
    }).catch(e => { console.log(e); });
});
buttonRange.addEventListener('click', event => {
    // override e-hentai's viewing shortcut
    document.onkeydown = undefined;
    const pageNum = $('div.sn').textContent.match(/(\d+)\s*\/\s*(\d+)/).slice(1);
    buttonRange.insertAdjacentHTML('afterend', `<span id="ehrsetrange"><input type="number" id="ehrstart" value="${pageNum[0]}" min="1" max="${pageNum[1]}"> - <input type="number" id="ehrstop" value="${pageNum[1]}" min="1" max="${pageNum[1]}"></span>`);
    buttonRange.remove();
});


/***/ })
/******/ ]);