Manga Loader

Loads manga chapter into one page in a long strip format, supports switching chapters and works for a variety of sites, minimal script with no dependencies, easy to implement new sites

Versione datata 16/12/2014. Vedi la nuova versione l'ultima versione.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name       Manga Loader
// @namespace  http://www.fuzetsu.com/MangaLoader
// @version    1.4.7
// @description  Loads manga chapter into one page in a long strip format, supports switching chapters and works for a variety of sites, minimal script with no dependencies, easy to implement new sites
// @copyright  2014+, fuzetsu
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_registerMenuCommand
// @match http://bato.to/read/*
// @match http://mangafox.me/manga/*/*/*
// @match http://readms.com/r/*/*/*/*
// @match http://g.e-hentai.org/s/*/*
// @match http://exhentai.org/s/*/*
// @match *://www.fakku.net/*/*/read*
// @match http://www.mangareader.net/*/*
// @match http://www.mangahere.co/manga/*/*
// @match http://www.mangapanda.com/*/*
// @match http://mangadeer.com/manga/*/*/*/*
// @match http://mangacow.co/*/*
// @match http://nowshelf.com/watch/*
// @match http://nhentai.net/g/*/*
// @match http://centraldemangas.net/online/*/*
// @match http://www.mangatown.com/manga/*/*/*
// @match http://mangajoy.com/*/*
// @match http://*.dm5.com/m*
// @match http://raw.senmanga.com/*/*/*
// @match http://www.japscan.com/lecture-en-ligne/*
// @match http://www.pecintakomik.com/manga/*/*
// @match http://dynasty-scans.com/chapters/*
// @match http://www.onemanga.me/*/*
// ==/UserScript==
// don't bother running if in a frame
if (window.top !== window.self) return;

// set to true for manga load without prompt
var BM_MODE = false;

var scriptName = 'Manga Loader';
var pageTitle = document.title;

/**
Sample Implementation:
{
    match: "http://domain.com/.*" // the url to react to for manga loading
  , img: '#image' // css selector to get the page's manga image
  , next: '#next_page' // css selector to get the link to the next page
  , numpages: '#page_select' // css selector to get the number of pages. elements like (select, span, etc)
  , curpage: '#page_select' // css selector to get the current page. usually the same as numPages if it's a select element
  , nextchap: '#next_chap' // css selector to get the link to the next chapter
  , prevchap: '#prev_chap' // same as above except for previous
  , wait: 3000 // how many ms to wait before auto loading (to wait for elements to load)
  , pages: function(next_url, current_page_number, callback, extract_function) {
    // gets called requesting a certain page number (current_page_number)
    // to continue loading execute callback with img to append as first parameter and next url as second parameter
    // only really needs to be used on sites that have really unusual ways of loading images or depend on javascript
  }

  Any of the CSS selectors can be functions instead that return the desired value.
}
*/

var implementations = [{ // Batoto
  match: "^http://bato.to/read/.*",
  img: '#comic_page',
  next: '#full_image + div > a',
  numpages: '#page_select',
  curpage: '#page_select',
  nextchap: 'select[name=chapter_select]',
  prevchap: 'select[name=chapter_select]',
  invchap: true
}, { // MangaPanda
  match: "^http://www.mangapanda.com/.*/[0-9]*",
  img: '#img',
  next: '.next a',
  numpages: '#pageMenu',
  curpage: '#pageMenu',
  nextchap: 'td.c5 + td a',
  prevchap: 'table.c6 tr:last-child td:last-child a'
}, { // MangaFox
  match: "^http://mangafox.me/manga/.*/.*/.*",
  img: '#image',
  next: '.next_page',
  numpages: 'select.m',
  curpage: 'select.m',
  nextchap: '#chnav p + p a',
  prevchap: '#chnav a'
}, { // MangaStream
  match: "^http://readms.com/r/.*/.*/.*",
  img: '#manga-page',
  next: '.next a',
  numpages: function() {
    var lastPage = getEl('.subnav-wrapper .controls .btn-group:last-child ul li:last-child');
    return parseInt(lastPage.textContent.match(/[0-9]/g).join(''), 10);
  },
  nextchap: function(prev) {
    var found;
    var chapters = [].slice.call(document.querySelectorAll('.controls > div:first-child > .dropdown-menu > li a'));
    chapters.pop();
    for (var i = 0; i < chapters.length; i++) {
      if (window.location.href.indexOf(chapters[i].href) !== -1) {
        found = chapters[i + (prev ? 1 : -1)];
        if (found) return found.href;
      }
    }
  },
  prevchap: function() {
    return this.nextchap(true);
  }
}, { // MangaReader
  match: "^http://www.mangareader.net/.*/.*",
  img: '#img',
  next: '.next a',
  numpages: '#pageMenu',
  curpage: '#pageMenu',
  nextchap: 'td.c5 + td a',
  prevchap: 'table.c6 tr:last-child td:last-child a'
}, { // MangaTown
  match: "^http://www.mangatown.com/manga/[^/]*/v[0-9]*/c[0-9]*",
  img: '#image',
  next: '#viewer a',
  numpages: '.page_select select',
  curpage: '.page_select select',
  nextchap: '#top_chapter_list',
  prevchap: '#top_chapter_list',
  wait: 1000
}, { // MangaCow
  match: "^http://mangacow\\.co/.*/[0-9]*",
  img: '.prw > a > img',
  next: '.prw > a:last-child',
  numpages: 'select.cbo_wpm_pag',
  curpage: 'select.cbo_wpm_pag',
  nextchap: function(prev) {
    var chapSel = getEl('select.cbo_wpm_chp');
    var nextChap = chapSel.options[chapSel.selectedIndex + (prev ? 1 : -1)];
    if (nextChap) {
      return 'http://mangacow.co/' + window.location.pathname.slice(1, window.location.pathname.slice(1).indexOf('/') + 2) + nextChap.value;
    }
  },
  prevchap: function() {
    return this.nextchap(true);
  }
}, { // MangaHere
  match: "^http://www.mangahere.co/manga/.*/.*",
  img: '#viewer img',
  next: '#viewer a',
  numpages: 'select.wid60',
  curpage: 'select.wid60',
  nextchap: function(prev) {
    var chapters = getEls('.reader_tip > p:nth-last-child(2) > a, .reader_tip > p:nth-last-child(1) > a'),
      tag = chapters[0] && chapters[0].previousElementSibling && chapters[0].previousElementSibling.textContent.trim();
    if (tag === 'Next Chapter:') {
      if (prev) {
        return chapters[1];
      } else {
        return chapters[0];
      }
    } else {
      if (prev) {
        return chapters[1];
      }
    }
  },
  prevchap: function() {
    return this.nextchap(true);
  }
}, { // MangaDeer
  match: "^http://mangadeer\\.com/manga/.*",
  img: '.img-link > img',
  next: '.page > span:last-child > a',
  numpages: '#sel_page_1',
  curpage: '#sel_page_1',
  nextchap: function(prev) {
    var ddl = getEl('#sel_book_1');
    var index = ddl.selectedIndex + (prev ? -1 : 1);
    if (index >= ddl.options.length) return;
    var mangaName = window.location.href.slice(window.location.href.indexOf('manga/') + 6);
    mangaName = mangaName.slice(0, mangaName.indexOf('/'));
    return 'http://mangadeer.com/manga/' + mangaName + ddl.options[index].value + '/1';
  },
  prevchap: function() {
    return this.nextchap(true);
  }
}, { // Central de Mangas
  match: "^http://centraldemangas.net/online/[^\\/]*/[0-9]*",
  img: '#manga-page',
  next: '#manga-page',
  numpages: '#manga_pages',
  curpage: '#manga_pages',
  nextchap: function(prev) {
    var url = window.location.href,
      chapters = getEl('#manga_caps'),
      urlPre = url.slice(0, url.lastIndexOf('/') + 1),
      newChap = chapters.options[chapters.selectedIndex + (prev ? -1 : 1)];
    return newChap ? urlPre + newChap.textContent : null;
  },
  prevchap: function() {
    return this.nextchap(true);
  },
  pages: function(url, num, cb, ex) {
    var url = url.slice(0, url.lastIndexOf('-') + 1) + ("0" + num).slice(-2) + url.slice(url.lastIndexOf('.'));
    cb(url, url);
  }
}, { // Manga Joy
  match: "^http://mangajoy.com/[^/]*/[0-9]*",
  img: '.prw img',
  next: '.nxt',
  numpages: '.wpm_pag.mng_rdr > div:nth-child(3) > ul > li:nth-child(3) > select',
  curpage: '.wpm_pag.mng_rdr > div:nth-child(3) > ul > li:nth-child(3) > select',
  nextchap: function(prev) {
    var chapter = extractInfo('.wpm_pag.mng_rdr > div:nth-child(3) > ul > li:nth-child(2) > select', {
      type: 'value',
      val: prev ? 1 : -1
    });
    if (chapter) {
      var urlParts = window.location.href.slice(7).split('/');
      while (urlParts.length > 2) urlParts.pop();
      return 'http://' + urlParts.join('/') + '/' + chapter;
    }
  },
  prevchap: function() {
    return this.nextchap(true);
  }
}, { // GEH/EXH
  match: "^http://(g.e-hentai|exhentai).org/s/.*/.*",
  img: '.sni > a > img, #img',
  next: '.sni > a, #i3 a'
}, { // Fakku
  match: "^http(s)?://www.fakku.net/.*/.*/read",
  img: '.current-page',
  next: '.current-page',
  numpages: '.drop',
  curpage: '.drop',
  pages: function(url, num, cb, ex) {
    var firstNum = url.lastIndexOf('/'),
      lastDot = url.lastIndexOf('.');
    var c = url.charAt(firstNum);
    while (c && !/[0-9]/.test(c)) {
      c = url.charAt(++firstNum);
    }
    var curPage = parseInt(url.slice(firstNum, lastDot), 10);
    var url = url.slice(0, firstNum) + ('00' + (curPage + 1)).slice(-3) + url.slice(lastDot);
    cb(url, url);
  }
}, { // Nowshelf
  match: "^http://nowshelf.com/watch/[0-9]*",
  img: '#image',
  next: '#image',
  numpages: function() {
    return parseInt(getEl('#page').textContent.slice(3), 10);
  },
  curpage: function() {
    return parseInt(getEl('#page > input').value, 10);
  },
  pages: function(url, num, cb, ex) {
    var url = url.slice(0, -7) + ('00' + num).slice(-3) + url.slice(-4);
    cb(url, url);
  }
}, { // nhentai
  match: "^http://nhentai\\.net\\/g\\/[0-9]*/[0-9]*",
  img: '#image-container > a > img',
  next: '#image-container > a > img',
  numpages: '.num-pages',
  curpage: '.current',
  pages: function(url, num, cb, ex) {
    url = url.replace(/\/[^\/]*$/, '/') + num;
    cb(url, url);
  }
}, { // dm5
  match: "^http://[^\\.]*\\.dm5\\.com/m[0-9]*",
  img: '#cp_image',
  next: '#cp_image',
  numpages: '#pagelist',
  curpage: '#pagelist',
  pages: function(url, num, cb, ex) {
    var cid = window.location.href.match(/m[0-9]*/g)[2].slice(1),
      xhr = new XMLHttpRequest();
    xhr.open('get', 'imagefun.ashx?cid=' + cid + '&page=' + num);
    xhr.onload = function() {
      var images = eval(xhr.responseText);
      console.log(self.images);
      cb(images[0], images[0]);
    };
    xhr.send();
  }
}, { // Senmanga
  match: "^http://raw\\.senmanga\\.com/[^/]*/[0-9]*/[0-9]*",
  img: '#picture',
  next: '#omv > table > tbody > tr:nth-child(2) > td > a',
  numpages: 'select[name=page]',
  curpage: 'select[name=page]',
  nextchap: function(prev) {
    var url = window.location.href,
      current = url.match(/\/([0-9]+)\//)[1];
    return window.location.href.replace(/[0-9]+\/[0-9]+\/?$/, '') + (parseInt(current) + (prev ? -1 : 1)) + '/1';
  },
  prevchap: function() {
    return this.nextchap(true);
  }
}, { // japscan
  match: "^http://www\\.japscan\\.com/lecture-en-ligne/[^/]*/[0-9]*",
  img: '#imgscan',
  next: '#next_link',
  numpages: '#pages',
  curpage: '#pages',
  nextchap: '#next_chapter',
  prevchap: '#back_chapter'
}, { // pecintakomik
  match: "^http://www\\.pecintakomik\\.com/manga/[^/]*/[^/]*",
  img: '.picture',
  next: '.pager a:nth-child(3)',
  numpages: 'select[name=page]',
  curpage: 'select[name=page]',
  nextchap: function(prev) {
    var chapters = getEl('select[name=chapter]'),
      chapter = chapters.options[chapters.selectedIndex + (prev ? 1 : -1)];
    if (chapter) {
      return window.location.href.replace(/\/[^\/]*\/[0-9]*\/?$/, '/' + chapter.value);
    }
  },
  prevchap: function() {
    return this.nextchap(true);
  }
}, { // dynasty-scans
  match: "^http://dynasty-scans.com/chapters/.*",
  img: '#image > img',
  next: '#image > img',
  numpages: function() {
    return unsafeWindow.pages.length;
  },
  curpage: function() {
    return parseInt(getEl('#image > div.pages-list > a.page.active').textContent);
  },
  nextchap: '#next_link',
  prevchap: '#prev_link',
  pages: function(url, num, cb, ex) {
    url = unsafeWindow.pages[num - 1].image;
    cb(url, url);
  }
}, { // OneManga
  match: "^http://www\\.onemanga\\.me/[^/]*/[0-9]*",
  img: 'img.manga-page',
  next: '.nav_pag > li:nth-child(1) > a',
  numpages: 'select.cbo_wpm_pag',
  curpage: 'select.cbo_wpm_pag',
  nextchap: function(prev) {
    var curChap = parseInt(extractInfo('select.cbo_wpm_chp', {type: 'value'})),
        targetChap = curChap + (prev ? -1 : 1);
    return window.location.href.replace(/\/[0-9]*(\/[0-9]*\/?)?$/, '/' + targetChap);
  },
  prevchap: function() {
    return this.nextchap(true);
  }
}];

var log = function(msg, type) {
  type = type || 'log';
  if (type === 'exit') {
    throw scriptName + ' exit: ' + msg;
  } else {
    console[type](scriptName + ' ' + type + ': ', msg);
  }
};

var getEl = function(q, c) {
  if (!q) return;
  return (c || document).querySelector(q);
};

var getEls = function(q, c) {
  return [].slice.call((c || document).querySelectorAll(q));
};

var storeGet = function(key) {
  if (typeof GM_getValue === "undefined") {
    var value = localStorage.getItem(key);
    if (value === "true" || value === "false") {
      return (value === "true") ? true : false;
    }
    return value;
  }
  return GM_getValue(key);
};

var storeSet = function(key, value) {
  if (typeof GM_setValue === "undefined") {
    return localStorage.setItem(key, value);
  }
  return GM_setValue(key, value);
};

var storeDel = function(key) {
  if (typeof GM_deleteValue === "undefined") {
    return localStorage.removeItem(key);
  }
  return GM_deleteValue(key);
};

var extractInfo = function(selector, mod, context) {
  selector = this[selector] || selector;
  if (typeof selector === 'function') {
    return selector.call(this);
  }
  var elem = getEl(selector, context),
    option;
  mod = mod || {};
  if (elem) {
    switch (elem.nodeName.toLowerCase()) {
      case 'img':
        return (mod.altProp && elem.getAttribute(mod.altProp)) || elem.src;
      case 'a':
        return elem.href;
      case 'ul':
        return elem.children.length;
      case 'select':
        switch (mod.type) {
          case 'index':
            return elem.options.selectedIndex + 1;
          case 'value':
            option = elem.options[elem.options.selectedIndex + (mod.val || 0)] || {};
            return option.value;
          default:
            return elem.options.length;
        }
        break;
      default:
        return elem.textContent;
    }
  }
};

var addStyle = function() {
  if (!this.loc) {
    this.loc = document.createElement('style');
    this.loc.dataset.name = 'ml-style';
    document.head.appendChild(this.loc);
  }
  this.loc.textContent += [].join.call(arguments, '\n');
};

var toStyleStr = function(obj, selector) {
  var stack = [],
    key;
  for (key in obj) {
    if (obj.hasOwnProperty(key)) {
      stack.push(key + ':' + obj[key]);
    }
  }
  if (selector) {
    return selector + '{' + stack.join(';') + '}';
  } else {
    return stack.join(';');
  }
};

var throttle = function(callback, limit) {
  var wait = false;
  return function() {
    if (!wait) {
      callback();
      wait = true;
      setTimeout(function() {
        wait = false;
      }, limit);
    }
  };
};

var createButton = function(text, action, styleStr) {
  var button = document.createElement('button');
  button.textContent = text;
  button.onclick = action;
  button.setAttribute('style', styleStr || '');
  return button;
};

var getViewer = function(prevChapter, nextChapter) {
  var viewerCss = toStyleStr({
      'background-color': 'black',
      'font': '0.813em courier',
      'text-align': 'center',
    }, 'body'),
    imagesCss = toStyleStr({
      'margin-top': '10px',
      'margin-bottom': '10px'
    }, '.ml-images'),
    imageCss = toStyleStr({
      'max-width': '100%',
      'display': 'block',
      'margin': '3px auto'
    }, '.ml-images img'),
    counterCss = toStyleStr({
      'background-color': '#222',
      'color': 'white',
      'border-radius': '10px',
      'width': '30px',
      'margin-left': 'auto',
      'margin-right': 'auto',
      'margin-top': '-12px',
      'padding-left': '5px',
      'padding-right': '5px',
      'border': '1px solid white',
      'z-index': '100',
      'position': 'relative'
    }, '.ml-counter'),
    navCss = toStyleStr({
      'text-decoration': 'none',
      'color': 'white',
      'background-color': '#444',
      'padding': '3px 10px',
      'border-radius': '5px',
      'transition': '250ms'
    }, '.ml-chap-nav a'),
    navHoverCss = toStyleStr({
      'background-color': '#555'
    }, '.ml-chap-nav a:hover'),
    statsCss = toStyleStr({
      'position': 'fixed',
      'bottom': '0',
      'right': '0',
      'background-color': '#222',
      'color': 'white',
      'padding': '7px',
      'border-top-left-radius': '5px',
      'opacity': '0.4',
      'transition': '250ms',
      'cursor': 'default'
    }, '.ml-stats'),
    statsCollapseCss = toStyleStr({
      'color': 'orange',
      'cursor': 'pointer'
    }, '.ml-stats-collapse'),
    statsHoverCss = toStyleStr({
      'opacity': '1'
    }, '.ml-stats:hover');
  // clear all styles and scripts
  var title = document.title;
  document.head.innerHTML = '<meta name="viewport" content="width=device-width, initial-scale=1">';
  document.title = title;
  // navigation
  var nav = '<div class="ml-chap-nav">' + (prevChapter ? '<a href="' + prevChapter + '">Prev Chapter</a> ' : '') +
    (storeGet('mAutoload') ? '' : '<a href="" data-ignore="true">Exit</a> ') +
    (nextChapter ? '<a href="' + nextChapter + '">Next Chapter</a>' : '') + '</div>';
  // stats
  var stats = '<div class="ml-stats"><span title="hide stats" class="ml-stats-collapse">&gt;&gt;</span><span class="ml-stats-content"></span></div>';
  // combine ui elements
  document.body.innerHTML = nav + '<div class="ml-images"></div>' + nav + stats;
  // add all styles to the page
  addStyle(viewerCss, imagesCss, imageCss, counterCss, navCss, navHoverCss, statsCss, statsCollapseCss, statsHoverCss);
  // set up return UI object
  var UI = {
    images: getEl('.ml-images'),
    statsContent: getEl('.ml-stats-content'),
    statsCollapse: getEl('.ml-stats-collapse')
  };
  // set up listeners
  document.addEventListener('click', function(evt) {
    if (evt.target.nodeName === 'A' && !evt.target.dataset.ignore && evt.target.parentNode.className.indexOf('ml-chap-nav') !== -1) {
      log('next chapter will autoload');
      storeSet('autoload', 'yes');
    }
  }, false);
  UI.statsCollapse.addEventListener('click', function(evt) {
    var test = UI.statsCollapse.textContent === '>>';
    UI.statsContent.style.display = test ? 'none' : '';
    UI.statsCollapse.textContent = test ? '<<' : '>>';
  }, false);
  return UI;
};

var getCounter = function(imgNum) {
  var counter = document.createElement('div');
  counter.classList.add('ml-counter');
  counter.textContent = imgNum;
  return counter;
};

var addImage = function(src, loc, imgNum, callback) {
  var image = new Image();
  image.onerror = function() {
    log('failed to load ' + src);
    image.remove();
  };
  image.onload = callback;
  image.src = src;
  loc.appendChild(image);
  loc.appendChild(getCounter(imgNum));
};

var loadManga = function(imp) {
  var ex = extractInfo.bind(imp),
    imgUrl = ex('img'),
    nextUrl = ex('next'),
    numPages = ex('numpages'),
    curPage = ex('curpage', {
      type: 'index'
    }) || 1,
    nextChapter = ex('nextchap', {
      type: 'value',
      val: (imp.invchap && -1) || 1
    }),
    prevChapter = ex('prevchap', {
      type: 'value',
      val: (imp.invchap && 1) || -1
    }),
    xhr = new XMLHttpRequest(),
    d = document.implementation.createHTMLDocument(),
    addAndLoad = function(img, next) {
      updateStats();
      addImage(img, UI.images, curPage, function() {
        pagesLoaded += 1;
        updateStats();
      });
      loadNextPage(next);
    },
    updateStats = function() {
      UI.statsContent.textContent = ' ' + pagesLoaded + '/' + curPage + ' loaded' + (numPages ? ', ' + numPages + ' total' : '');
    },
    getPageInfo = function() {
      var page = d.body;
      d.body.innerHTML = xhr.response;
      try {
        // find image and link to next page
        addAndLoad(ex('img', imp.imgmod, page), ex('next', null, page));
      } catch (e) {
        log('error getting details from next page, assuming end of chapter.');
      }
    },
    loadNextPage = function(url) {
      if (mLoadLess && count % loadInterval === 0) {
        if (resumeUrl) {
          resumeUrl = null;
        } else {
          resumeUrl = url;
          log('waiting for user to scroll further before loading more images, loaded ' + count + ' pages so far, next url is ' + resumeUrl);
          return;
        }
      }
      if (curPage + 1 > numPages) {
        log('reached "numPages" ' + numPages + ', assuming end of chapter');
        return;
      }
      if (lastUrl === url) {
        log('last url is the same as current, assuming end of chapter');
        return;
      }
      curPage += 1;
      count += 1;
      lastUrl = url;
      if (imp.pages) {
        imp.pages(url, curPage, addAndLoad, ex);
      } else {
        xhr.open('get', url);
        xhr.onload = getPageInfo;
        xhr.onerror = function() {
          log('failed to load page, aborting', 'error');
        };
        xhr.send();
      }
    },
    count = 1,
    pagesLoaded = curPage - 1,
    loadInterval = 10,
    lastUrl, UI, resumeUrl;

  if (!imgUrl || !nextUrl) {
    log('failed to retrieve ' + (!imgUrl ? 'image url' : 'next page url'), 'exit');
  }

  UI = getViewer(prevChapter, nextChapter);

  UI.statsContent.textContent = ' 0/1 loaded, ' + numPages + ' total';

  if (mLoadLess) {
    window.onscroll = throttle(function(e) {
      if (!resumeUrl) return; // exit early if we don't have a position to resume at
      var scrollBottom = document.body.scrollHeight - ((document.body.scrollTop || document.documentElement.scrollTop) + window.innerHeight);
      if (scrollBottom < 4500) {
        log('user scroll nearing end, loading more images starting from ' + resumeUrl);
        loadNextPage(resumeUrl);
      }
    }, 100);
  }

  addAndLoad(imgUrl, nextUrl);

};

var pageUrl = window.location.href,
  btnLoadCss = toStyleStr({
    'position': 'fixed',
    'bottom': 0,
    'right': 0,
    'padding': '5px',
    'margin': '0 10px 10px 0',
    'z-index': '99999'
  }),
  btnLoad;

// used when switching chapters
var autoload = storeGet('autoload');
// manually set by user in menu
var mAutoload = storeGet('mAutoload') || false;
// should we load less pages at a time?
var mLoadLess = storeGet('mLoadLess') === false ? false : true;

// clear autoload
storeDel('autoload');

// register menu commands
if (typeof GM_registerMenuCommand === 'function') {
  GM_registerMenuCommand('ML: ' + (mAutoload ? 'Disable' : 'Enable') + ' manga autoload', function() {
    storeSet('mAutoload', !mAutoload);
    window.location.reload();
  });
  GM_registerMenuCommand('ML: Load ' + (mLoadLess ? 'full chapter in one go' : '10 pages at a time'), function() {
    storeSet('mLoadLess', !mLoadLess);
    window.location.reload();
  });
}

log('starting...');

var success = implementations.some(function(imp) {
  if (imp.match && (new RegExp(imp.match, 'i')).test(pageUrl)) {
    if (BM_MODE || mAutoload || autoload) {
      log('autoloading...');
      setTimeout(loadManga.bind(null, imp), imp.wait || 0);
      return true;
    }
    // append button to dom that will trigger the page load
    btnLoad = createButton('Load Manga', function(evt) {
      loadManga(imp);
      this.remove();
    }, btnLoadCss);
    document.body.appendChild(btnLoad);
    return true;
  }
});

if (!success) {
  log('no implementation for ' + pageUrl, 'error');
}