e-hentai retriever

e-hentai & exhentai image url retriever

2017-08-30 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

// ==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();
});


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