Manga Loader NSFW

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, loads quickly and works on mobile devices through bookmarklet

Verze ze dne 18. 07. 2016. Zobrazit nejnovější verzi.

// ==UserScript==
// @name       Manga Loader NSFW
// @namespace  http://www.fuzetsu.com/MangaLoaderNSFW
// @version    1.0.42
// @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, loads quickly and works on mobile devices through bookmarklet
// @copyright  2016+, fuzetsu
// @noframes
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// -- NSFW START
// @match *://dynasty-scans.com/chapters/*
// @match *://hentaifr.net/*
// @match *://prismblush.com/comic/*
// @match *://www.hentairules.net/galleries*/picture.php*
// @match *://pururin.com/view/*
// @match *://hitomi.la/reader/*
// @match *://www.doujin-moe.us/*
// @match *://www.8muses.com/picture/*/*/*/*
// @match *://nowshelf.com/watch/*
// @match *://nhentai.net/g/*/*
// @match *://g.e-hentai.org/s/*/*
// @match *://exhentai.org/s/*/*
// @match *://www.fakku.net/*/*/read*
// @match *://hentaihere.com/m/*/*/*
// @match *://www.hentaihere.com/m/*/*/*
// @match *://*.tsumino.com/Read/View/*
// @match *://www.hentaibox.net/*/*
// @match *://*.hentai-free.org/*
// @match *://*.mangafap.com/image/*
// @match *://*.hentai4manga.com/hentai_manga/*
// @match *://*.heymanga.xyz/manga/*
// -- NSFW END
// -- FOOLSLIDE NSFW START
// @match *://reader.yuriproject.net/read/*
// @match *://ecchi.japanzai.com/read/*
// @match *://h.japanzai.com/read/*
// @match *://reader.japanzai.com/read/*
// @match *://yomanga.co/reader/read/*
// @match *://raws.yomanga.co/read/*
// @match *://hentai.cafe/manga/read/*
// @match *://*.yuri-ism.net/slide/read/*
// -- FOOLSLIDE NSFW END
// @require https://greasyfork.org/scripts/692-manga-loader/code/Manga%20Loader.user.js?24
// ==/UserScript==

/**
Sample Implementation:
{
    name: 'something' // name of the implementation
  , match: "^https?://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
  , numchaps: '#chapters' // css selector to get the number of chapters in manga
  , curchap: '#chapters' // css selector to get the number of the current chapter
  , 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), or a css selector to keep trying until it returns an elem
  , 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.
}
*/

// reusable functions to insert in implementations
var reuse = {
  encodeChinese: function(xhr) {
    xhr.overrideMimeType('text/html;charset=gbk');
  },
  na: function() {
    return 'N/A';
  }
};

var nsfwimp = [{
  name: 'geh-and-exh',
  match: "^https?://(g.e-hentai|exhentai).org/s/.*/.*",
  img: '.sni > a > img, #img',
  next: '.sni > a, #i3 a',
  numpages: 'body > div > div:nth-child(2) > div > span:nth-child(2)',
  curpage: 'body > div > div:nth-child(2) > div > span:nth-child(1)'
}, {
  name: '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);
    url = url.slice(0, firstNum) + ('00' + (curPage + 1)).slice(-3) + url.slice(lastDot);
    cb(url, url);
  }
}, {
  name: 'nowshelf',
  match: "^https?://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) {
    cb(page[num], num);
  }
}, {
  name: 'nhentai',
  match: "^https?://nhentai\\.net\\/g\\/[0-9]+/[0-9]+",
  img: '#image-container > a img',
  next: '#image-container > a',
  numpages: '.num-pages',
  curpage: '.current',
  imgmod: {
    altProp: 'data-cfsrc'
  },
}, {
  name: '8muses',
  match: "^http(s)?://www.8muses.com/picture/[^/]+/[^/]+/[^/]+/.+",
  img: '#image',
  next: '#next_picture',
  numpages: '#main > aside > div.pages-row.mobile-hidden > select'
}, {
  name: 'hitomi',
  match: "^http(s)?://hitomi.la/reader/[0-9]+.html",
  img: '#comicImages > img',
  next: '#comicImages > img',
  numpages: function() {
    return W.images.length;
  },
  curpage: function() {
    return parseInt(W.curPanel);
  },
  pages: function(url, num, cb, ex) {
    cb(W.images[num - 1].path, num);
  },
  wait: '#comicImages > img'
}, {
  name: 'doujin-moe',
  _pages: null,
  match: "^https?://www.doujin-moe.us/.+",
  img: 'img.picture',
  next: 'img.picture',
  numpages: function() {
    if (!this._pages) {
      this._pages = getEls('#gallery djm').map(function(file) {
        return file.getAttribute('file');
      });
    }
    return this._pages.length;
  },
  curpage: function() {
    return parseInt(getEl('.counter').textContent.match(/^[0-9]+/)[0]);
  },
  pages: function(url, num, cb, ex) {
    cb(this._pages[num - 1], num);
  }
}, {
  name: 'pururin',
  match: "^https?://pururin\\.com/view/.+\\.html",
  img: '.image img',
  next: 'a.image-next',
  numpages: 'select.image-pageSelect',
  curpage: 'select.image-pageSelect'
}, {
  name: 'hentai-rules',
  match: "^https?://www\\.hentairules\\.net/galleries[0-9]*/picture\\.php.+",
  img: '#theMainImage',
  next: '#linkNext',
  imgmod: {
    altProp: 'data-src'
  },
  numpages: function(cur) {
    return parseInt(getEl('.imageNumber').textContent.replace(/([0-9]+)\/([0-9]+)/, cur ? '$1' : '$2'));
  },
  curpage: function() {
    return this.numpages(true);
  }
}, {
  name: 'ero-senmanga',
  match: "^https?://ero\\.senmanga\\.com/[^/]*/[^/]*/[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 next = extractInfo('select[name=chapter]', {
      type: 'value',
      val: (prev ? -1 : 1)
    });
    if (next) return window.location.href.replace(/\/[^\/]*\/[0-9]+\/?$/, '') + '/' + next + '/1';
  },
  prevchap: function() {
    return this.nextchap(true);
  }
}, {
  name: 'hentaifr',
  match: "^https?://hentaifr\\.net/.+\\.php\\?id=[0-9]+",
  img: function(ctx, next) {
    var img = getEls('img[width]', ctx).filter(function(img) {
      return img.getAttribute('src').indexOf('contenu/doujinshis') !== -1;
    })[0];
    return next ? img : (img ? img.getAttribute('src') : null);
  },
  next: function(ctx) {
    var img = this.img(ctx, true);
    return img ? img.parentNode.getAttribute('href') : null;
  },
  wait: function() {
    return this.img() &&  this.next();
  }
}, {
  name: 'prism-blush',
  match: "^https?://prismblush.com/comic/.+",
  img: '#comic img',
  next: '#comic a'
}, {
  name: 'hentai-here',
  match: "^https?://(www\\.)?hentaihere.com/m/[^/]+/[0-9]+/[0-9]+",
  img: '#arf-reader-img',
  next: '#arf-reader-img',
  curpage: function() {
    return parseInt(W.rff_thisIndex);
  },
  numpages: function() {
    return W.rff_imageList.length;
  },
  pages: function(url, num, cb, ex) {
    cb(W.imageCDN + W.rff_imageList[num - 1], num);
  },
  nextchap: function() {
    return W.rff_nextChapter;
  },
  prevchap: function() {
    return W.rff_previousChapter;
  },
  curchap: function() {
    var curchap;
    getEls('ul.dropdown-menu.text-left li').some(function(li, index) {
      if(getEl('a.bg-info', li)) {
        curchap = index + 1;
      }
    });
    return curchap;
  },
  numchaps: 'ul.dropdown-menu.text-left',
  wait: 'ul.dropdown-menu.text-left'
}, {
  name: 'foolslide',
  match: "^https?://(" + [
    'reader.yuriproject.net/read/.+',
    'ecchi.japanzai.com/read/.+',
    'h.japanzai.com/read/.+',
    'reader.japanzai.com/read/.+',
    '(raws\\.)?yomanga.co(/reader)?/read/.+',
    'hentai.cafe/manga/read/.+',
    '(www\\.)?yuri-ism\\.net/slide/read/.'
  ].join('|') + ")",
  img: function() {
    return W.pages[W.current_page].url;
  },
  next: function() {
    return 'N/A';
  },
  numpages: function() {
    return W.pages.length;
  },
  curpage: function() {
    return W.current_page + 1;
  },
  nextchap: function(prev) {
    var desired;
    var dropdown = getEls('ul.dropdown')[1] || getEls('ul.uk-nav')[1];
    if(!dropdown) return;
    getEls('a', dropdown).forEach(function(chap, idx, arr) {
      if(location.href.indexOf(chap.href) === 0) desired = arr[idx + (prev ? 1 : -1)];
    });
    return desired && desired.href;
  },
  prevchap: function() {
    return this.nextchap(true);
  },
  pages: function(url, num, cb, ex) {
    cb(W.pages[num - 1].url, num);
  },
  wait: function() {
    if(W.location.href.indexOf('yomanga') !== -1) {
      // select all possible variable names for data structure
      var matches = [].slice.call(document.body.innerHTML.match(/\w+\s*=\s*\[\{"id"/ig));
      // extract actual variable name from match
      var tokens = matches.map(function(m) {
        var tok = m.match(/^(\w+)\s*=/i);
        return tok && tok[1];
      });
      // determine the variable that holds the largest data structure
      var largest;
      var max = 0;
      tokens.forEach(function(token) {
        var cur = W[token];
        if(cur && cur.length > max && [].every.call(cur, function(pg) { return pg.url.endsWith(pg.filename); })) {
          //console.log(typeof cur, typeof cur.every, cur.every, typeof [], [].every, cur);
          //console.log(cur.every(function(pg) { return pg.url.endsWith(pg.filename); }));
          max = cur.length;
          largest = cur;
        }
      });
      W.pages = largest || 'fail';
    }
    return W.pages;
  }
}, {
  name: 'tsumino',
  match: '^https?://(www\\.)?tsumino.com/Read/View/.+',
  img: '.reader-img',
  next: '.reader-img',
  numpages: function(curpage) {
    return W.reader_max_page;
  },
  curpage: function() {
    return W.reader_current_pg;
  },
  pages: function(url, num, cb) {
    var self = this;
    if(!self._pages) {
      ajax({
        method: 'POST',
        url: '/Read/Load',
        data: 'q=' + W.location.href.match(/View\/(\d+)/)[1],
        responseType: 'json',
        beforeSend: function(xhr) {
          xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        },
        onload: function(e) {
          var res = e.target.response;
          if(!res) return log('failed to load tsumino pages, site has probably been updated, report on forums', 'error');
          self._pages = res.reader_page_urls.map(function(page) {
            return W.reader_baseobj_url + '?name=' + encodeURIComponent(page);
          });
          cb(self._pages[num - 1], num);
        }
      });
    } else {
      cb(self._pages[num - 1], num);
    }
  },
  wait: '.reader-img'
}, {
  name: 'dynasty-scans',
  match: "^https?://dynasty-scans.com/chapters/.*",
  img: '#image > img',
  next: '#image > img',
  numpages: function() {
    return W.pages.length;
  },
  curpage: function() {
    return parseInt(getEl('#image > div.pages-list > a.page.active').getAttribute('href').slice(1));
  },
  nextchap: '#next_link',
  prevchap: '#prev_link',
  pages: function(url, num, cb, ex) {
    url = W.pages[num - 1].image;
    cb(url, url);
  }
}, {
  name: 'hentaibox',
  match: 'https?://www\\.hentaibox\\.net/hentai-manga/[^/]+/[0-9]+',
  img: 'td > center > a > img',
  next: 'td > center > a',
  numpages: function(cur) {
    var sel = getEl('select[name=np2]');
    if(sel) {
      var info = sel.options[0].textContent.match(/Page ([0-9]+) out of ([0-9]+)/i);
      if(info && info.length >= 3) return parseInt(cur ? info[1] : info[2]);
    }
  },
  curpage: function() {
    return this.numpages(true);
  }
}, {
  name: 'hentai-free', // trying to only show button on manga pages, but URL scheme makes it tough.
  match: "^http://hentai-free.org/(?!(?:tag|manga-doujinshi|hentai-video|hentai-wall|tags-map)/|.*\\?).+/$",
  img: function() {
    return W.pages[0];
  },
  next: reuse.na,
  numpages: function() {
    return W.pages.length;
  },
  curpage: function() {
    return 1;
  },
  wait: function() {
    var links = getEls('.gallery-item a, a.bwg_lightbox_0');
    if (links.length > 0) {
      W.pages = links.map(function(el) {
        return el.href.replace(/\?.*$/, '');
      });
      return true;
    }
    return false;
  },
  pages: function(url, num, cb, ex) {
    cb(W.pages[num - 1], num);
  }
}, {
  name: 'mangafap',
  match: "^https?://mangafap\\.com/image/.+",
  img: '#p',
  next: '#lbanner + div > a',
  numpages: 'select.span2',
  curpage: '.pagination li.active a'
}, {
  name: 'hentai4manga',
  match: "^https?://hentai4manga\\.com/hentai_manga/.+/\\d+/$",
  img: '#textboxContent img',
  next: '#textboxContent a',
  numpages: '#sl',
  curpage: '#sl'
}, {
  name: 'heymanga',
  match: "https?://(www\\.)?heymanga\\.xyz/manga/[^/]+/[0-9.]+/[0-9]+",
  img: '#img-content',
  next: function(context) {
    var num = this.curpage(context) + 1;
    return document.location.href.replace(/([0-9]+)$/, num);
  },
  numpages: function() {
    return getEl('#page_list').length - 1;
  },
  curpage: function(context) {
    var match = getEls('#page_list > option[selected]', context);
    return parseInt(match[match.length - 1].value); // dumb site always marks page 1 as "selected" in addition to actual selected page
  },
  nextchap: 'section > .row.text-center > p + a, section > .row.text-center .ti-line-dotted + a',
  prevchap: 'section > .row.text-center .ti-hand-point-left + a',
  wait: '#page_list > option[selected]'
}];

log('loading nsfw implementations...');
MLoaderLoadImps(nsfwimp);