// ==UserScript==
// @name Manga OnlineViewer
// @author Tago
// @namespace https://github.com/TagoDR
// @description Shows all pages at once in online view for these sites: Asura Scans, Batoto, ComiCastle, Dynasty-Scans, Asura Scans, Flame Scans, Realm Scans, Voids-Scans, Luminous Scans, InManga, KLManga, Leitor, LHTranslation, MangaBuddy, MangaDex, MangaFox, MangaHere, MangaFreak, Mangago, mangahosted, MangaHub, MangaKakalot, MangaNelo, MangaNato, MangaPark, MReader, Mangareader, MangaSee, Manga4life, MangaTigre, MangaTown, ManhuaScan, NineManga, PandaManga, RawDevart, ReadComicsOnline, ReadManga Today, Funmanga, MangaDoom, MangaInn, ReaperScans, SenManga(Raw), ShimadaScans, KLManga, TenManga, TuMangaOnline, UnionMangas, WebToons, Manga33, ZeroScans, FoOlSlide, Kireicake, Madara WordPress Plugin, MangaHaus, Isekai Scan, Comic Kiba, Zinmanga, mangatx, Toonily, Mngazuki, JaiminisBox, DisasterScans, ManhuaPlus
// @version 2022.11.22
// @license MIT
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_listValues
// @grant GM_deleteValue
// @grant GM_xmlhttpRequest
// @connect *
// @require https://cdnjs.cloudflare.com/ajax/libs/tinycolor/1.4.2/tinycolor.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery.imagesloaded/5.0.0/imagesloaded.pkgd.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.9.1/jszip.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/nprogress/0.2.0/nprogress.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/limonte-sweetalert2/11.4.35/sweetalert2.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js
// @include /https?:\/\/beta.asurascans.com\/read\/.+\/.+/
// @include /https?:\/\/(www.)?bato.to\/chapter.*/
// @include /https?:\/\/(www.)?comicastle.org\/read\/.+\/[0-9]+.*/
// @include /https?:\/\/(www.)?dynasty-scans.com\/chapters\/.+/
// @include /https?:\/\/(www.)?(asura|flamescans|realmscans|void-scans|luminousscans).(com|org|gg)\/.+/
// @include /https?:\/\/(www.)?inmanga.com\/ver\/manga\/.+\/.+/
// @include /https?:\/\/(www.)?klmanga.com\/.+chapter.+/
// @include /https?:\/\/(www.)?leitor.net\/manga\/.+\/.+\/.+/
// @include /https?:\/\/(www.)?lhtranslation.net\/read.+/
// @include /https?:\/\/(www.)?mangabuddy.com\/.+\/chapter.+/
// @include /https?:\/\/(www.)?mangadex.org\/chapter\/.+(\/.+)?/
// @include /https?:\/\/(www.)?(fanfox.net|mangahere.cc)\/manga\/.+\/.+\//
// @include /https?:\/\/.{3,4}?(mangafreak).net\/Read.+/
// @include /https?:\/\/(www.)?mangago.me\/.*\/.*\/.*/
// @include /https?:\/\/(www.)?mangahosted.com\/manga\/.+\/.+/
// @include /https?:\/\/(www.)?(mangahub).io\/chapter\/.+\/.+/
// @include /https?:\/\/(www.)?((manganelo|mangakakalot).com\/chapter\/.+\/.+|(manganato|readmanganato|chapmanganato).com\/manga-\w\w\d+\/chapter-\d+)/
// @include /https?:\/\/(www.)?mangapark.(com|me|org|net)\/(manga|chapter|comic)\/.+\/.+/
// @include /https?:\/\/(www.)?mreader.co\/reader\/.*/
// @include /https?:\/\/(www.)?mangareader.to\/read\/.+\/.+\/.+/
// @include /https?:\/\/(www.)?(mangasee123|manga4life).com\/read-online\/.+/
// @include /https?:\/\/(www.)?mangatigre.net\/.+\/.+\/.+/
// @include /https?:\/\/(www.|m.)?mangatown.com\/manga\/.+\/.+/
// @include /https?:\/\/(www.)?manhuascan.io\/.+chapter.+/
// @include /https?:\/\/(www.)?ninemanga.com\/chapter\/.+\/.+\.html/
// @include /https?:\/\/(www.)?pandamanga.xyz\/.+\/.+\/.+/
// @include /https?:\/\/(www.)?rawdevart.com\/comic\/.+\/.+\//
// @include /https?:\/\/(www.)?readcomicsonline.ru\/comic\/.*\/\d*/
// @include /https?:\/\/(www.)?(funmanga|mngdoom|readmng|mangainn).(com|net)\/.+\/\d+/
// @include /https?:\/\/(www.)?reaperscans.com\/comics\/.+\/chapters\/.+/
// @include /https?:\/\/raw.senmanga.com\/.+\/.+\/?/
// @include /https?:\/\/(www.)?shimadascans.com\/.+series.+/
// @include /https?:\/\/(www.)?tapas.io\/episode\/.+/
// @include /https?:\/\/(www.)?(tenmanga|gardenmanage).com\/(chapter|statuses)\/.+/
// @include /https?:\/\/(www.)?(tmofans|lectortmo|followmanga).com\/.+\/.+\/(paginated|cascade)/
// @include /https?:\/\/(www.)?unionleitor.top\/leitor\/.+\/.+/
// @include /https?:\/\/(www.)?webtoons.com\/.+viewer.+/
// @include /https?:\/\/(www.)?(manga33).com\/manga\/.+/
// @include /https?:\/\/(www.)?zeroscans.com\/comics\/.+/
// @include /^(?!.*jaiminisbox).*\/read\/.+/
// @include /https?:\/\/.+\/(manga|series)\/.+\/.+/
// @exclude /https?:\/\/(www.)?tsumino.com\/.+/
// @exclude /https?:\/\/(www.)?pururin.io\/.+/
// @downloadURL none
// ==/UserScript==
(function () {
'use strict';
// == AsuraScans ===================================================================================
var asurascans = {
name: 'Asura Scans',
url: /https?:\/\/beta.asurascans.com\/read\/.+\/.+/,
homepage: 'https://beta.asurascans.com/',
language: ['English'],
category: 'manga',
run() {
const { chapterID, data } = JSON.parse(document.querySelector('#__NEXT_DATA__')?.textContent ?? '').props.pageProps;
return {
title: document.querySelector('span.h2')?.textContent?.trim(),
series: document.querySelector('.container a.h6')?.getAttribute('href'),
pages: data.length,
prev: document.querySelector('.prev:not(.disabled)')?.getAttribute('href'),
next: document.querySelector('.next:not(.disabled)')?.getAttribute('href'),
listImages: data.map((img) => `https://img.asurascans.com/pages/${chapterID}/${img.uuid}.jpg`),
};
},
};
// == Batoto =======================================================================================
var batoto = {
name: 'Batoto',
url: /https?:\/\/(www.)?bato.to\/chapter.*/,
homepage: 'http://bato.to/',
language: ['English'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('.page-img')];
return {
title: document.querySelector('.nav-title a')?.textContent?.trim(),
series: document.querySelector('.nav-title a')?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('.nav-prev a')?.getAttribute('href'),
next: document.querySelector('.nav-next a')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
// == ComiCastle ===================================================================================
var comicastle = {
name: 'ComiCastle',
url: /https?:\/\/(www.)?comicastle.org\/read\/.+\/[0-9]+.*/,
homepage: 'http://www.comicastle.org/',
language: ['English'],
category: 'comic',
waitEle: '.form-control option:nth-child(1)',
run() {
const images = [...document.querySelectorAll('.form-control')[1].querySelectorAll('option')];
const chapter = document.querySelectorAll('.form-control')[0].querySelector('option:checked');
return {
title: chapter?.textContent?.trim(),
series: document.querySelector('.navbar-header a')?.getAttribute('href'),
pages: images.length,
prev: chapter?.previousElementSibling?.getAttribute('value'),
next: chapter?.nextElementSibling?.getAttribute('value'),
listImages: images.map((img) => img.getAttribute('alt')),
};
},
};
// == DynastyScans =================================================================================
var dysnatyscans = {
name: 'Dynasty-Scans',
url: /https?:\/\/(www.)?dynasty-scans.com\/chapters\/.+/,
homepage: 'https://dynasty-scans.com/',
language: ['English'],
category: 'manga',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
return {
title: document.querySelector('#chapter-title')?.textContent?.trim(),
series: document.querySelector('#chapter-title a')?.getAttribute('href'),
pages: W.pages.length,
prev: document.querySelector('#prev_link')?.getAttribute('href'),
next: document.querySelector('#next_link')?.getAttribute('href'),
listImages: W.pages.map((x) => x.image),
};
},
};
// == FlameScans ===================================================================================
var flamecans = {
name: ['Asura Scans', 'Flame Scans', 'Realm Scans', 'Voids-Scans', 'Luminous Scans'],
url: /https?:\/\/(www.)?(asura|flamescans|realmscans|void-scans|luminousscans).(com|org|gg)\/.+/,
homepage: [
'https://www.asura.gg/',
'https://flamescans.org/',
'https://realmscans.com/',
'https://void-scans.com/',
'https://luminousscans.com/',
],
language: ['English'],
category: 'manga',
waitEle: '#chapter option:nth-child(2)',
run() {
const chapter = document.querySelector('#chapter option:checked');
const images = [...document.querySelectorAll('#readerarea img')];
return {
title: document.querySelector('.entry-title')?.textContent?.trim(),
series: document.querySelector('.allc a')?.getAttribute('href'),
pages: images.length,
prev: chapter?.nextElementSibling?.getAttribute('value'),
next: chapter?.previousElementSibling?.getAttribute('value'),
listImages: images.map((img) => img.getAttribute('data-src') || img.getAttribute('src')),
};
},
};
// == FoOlSlide ====================================================================================
var foolslide = {
name: ['FoOlSlide', 'Kireicake'],
url: /^(?!.*jaiminisbox).*\/read\/.+/,
homepage: ['#', 'https://reader.kireicake.com'],
language: ['English'],
obs: 'Any Site that uses FoOLSlide',
category: 'manga',
waitEle: 'img.open',
run() {
const chapter = [...document.querySelectorAll('.topbar_left .dropdown_parent:last-of-type li')];
const origin = chapter.findIndex((item) => {
const url = item.querySelector('a')?.getAttribute('href');
if (url)
return window.location.href.startsWith(url);
return false;
});
const pages = [...document.querySelectorAll('.topbar_right .dropdown li')];
const images = [...document.querySelectorAll('.inner img:not(.open)')];
const num = images.length > 1 ? images.length : pages.length;
return {
title: chapter.at(origin)?.querySelector('a')?.textContent?.trim() ??
document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('div.tbtitle div.text a')?.getAttribute('href'),
pages: num,
prev: chapter
.at(origin + 1)
?.querySelector('a')
?.getAttribute('href'),
next: chapter
.at(origin - 1)
?.querySelector('a')
?.getAttribute('href'),
listPages: images.length > 1
? null
: Array(num)
.fill(0)
.map((_, i) => `${window.location.href.replace(/\/\d+$/, '')}/${i + 1}`),
listImages: images.length > 1 ? images.map((img) => img.getAttribute('src')) : null,
img: 'img.open',
};
},
};
// == InManga ===================================================================================
/* eslint-disable no-underscore-dangle */
var inmanga = {
name: 'InManga',
url: /https?:\/\/(www.)?inmanga.com\/ver\/manga\/.+\/.+/,
homepage: 'https://inmanga.com//',
language: ['Spanish'],
category: 'manga',
waitVar: 'pageController',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const images = [...document.querySelectorAll('#PageList option')];
const chapter = document.querySelector('#ChapList option:checked');
const src = W.pageController._containers.pageUrl;
return {
title: document.querySelector('title')?.textContent?.trim(),
series: `../${W.pageController._containers.mangaIdentification}`,
pages: images.length,
prev: chapter?.previousElementSibling?.getAttribute('value'),
next: chapter?.nextElementSibling?.getAttribute('value'),
listImages: images.map((img, index) => src.replace('identification', img.getAttribute('value')).replace('pageNumber', index)),
};
},
};
// == KLManga ======================================================================================
var klmanga = {
name: 'KLManga',
url: /https?:\/\/(www.)?klmanga.com\/.+chapter.+/,
homepage: 'https://klmanga.com/',
language: ['Raw'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('.chapter-content img')];
const chapter = document.querySelectorAll('.form-control')[0].querySelector('option:checked');
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.navbar-brand')?.getAttribute('href'),
pages: images.length,
prev: chapter?.nextElementSibling?.getAttribute('value'),
next: chapter?.previousElementSibling?.getAttribute('value'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
// == Leitor =======================================================================================
var leitor = {
name: 'Leitor',
url: /https?:\/\/(www.)?leitor.net\/manga\/.+\/.+\/.+/,
homepage: 'https://leitor.net/',
language: ['Portuguese'],
category: 'manga',
async run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const url = `https://leitor.net/leitor/pages/${W.READER_ID_RELEASE}.json?key=${W.READER_TOKEN}`;
const api = await fetch(url).then((res) => res.json());
const chapter = document.querySelector('.chapter-list .selected');
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.series-cover a')?.getAttribute('href'),
pages: api.images.length,
prev: chapter?.nextElementSibling?.querySelector('a')?.getAttribute('href'),
next: chapter?.previousElementSibling?.querySelector('a')?.getAttribute('href'),
listImages: api.images.map((img) => img.avif || img.legacy),
};
},
};
// == LHTranslation ================================================================================
var lhtranslation = {
name: 'LHTranslation',
url: /https?:\/\/(www.)?lhtranslation.net\/read.+/,
homepage: 'https://lhtranslation.net/',
language: ['English'],
category: 'manga',
run() {
const chapter = document.querySelector('.form-control option:checked');
const images = [...document.querySelectorAll('img.chapter-img')];
return {
title: document.querySelector('.chapter-img.tieude font')?.textContent?.trim(),
series: document.querySelector('.navbar-brand.manga-name')?.getAttribute('href'),
pages: images.length,
prev: chapter?.nextElementSibling?.getAttribute('value'),
next: chapter?.previousElementSibling?.getAttribute('value'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
// == Madara WordPress Plugin ======================================================================
// https://themeforest.net/item/madara-wordpress-theme-for-manga/20849828
var madarawp = {
name: [
'Madara WordPress Plugin',
'MangaHaus',
'Isekai Scan',
'Comic Kiba',
'Zinmanga',
'mangatx',
'Toonily',
'Mngazuki',
'JaiminisBox',
'DisasterScans',
'ManhuaPlus',
],
url: /https?:\/\/.+\/(manga|series)\/.+\/.+/,
homepage: [
'#',
'https://manhuaus.com',
'https://isekaiscan.com/',
'https://comickiba.com/',
'https://zinmanga.com/',
'https://mangatx.com/',
'https://toonily.net/',
'https://mangazuki.me/',
'https://jaiminisbox.net',
'https://disasterscans.com/',
'https://manhuaplus.com/',
],
language: ['English'],
obs: 'Any Site that uses Madara Wordpress Plugin',
category: 'manga',
run() {
const images = [
...document.querySelectorAll('.wp-manga-chapter-img, .blocks-gallery-item img, .reading-content img'),
];
return {
title: document.querySelector('#chapter-heading')?.textContent?.trim(),
series: (document.querySelector('.breadcrumb li:nth-child(3) a') ||
document.querySelector('.breadcrumb li:nth-child(2) a'))?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('.prev_page')?.getAttribute('href'),
next: document.querySelector('.next_page')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('src') ||
img.getAttribute('data-src') ||
img.getAttribute('data-full-url')),
};
},
};
// == MangaBuddy ===================================================================================
var mangabuddy = {
name: 'MangaBuddy',
url: /https?:\/\/(www.)?mangabuddy.com\/.+\/chapter.+/,
homepage: 'https://mangabuddy.com/',
language: ['English'],
category: 'manga',
waitVar: 'final_images',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const img = [...document.querySelectorAll('.chapter-image.load-first img')].map((img) => img.getAttribute('data-src') || img.getAttribute('src'));
const images = [...img, ...W.final_images];
return {
title: document.querySelector('.chapter-info')?.textContent?.trim(),
series: document
.querySelector('#breadcrumbs-container div:nth-child(2) a')
?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('a.prev')?.getAttribute('href'),
next: document.querySelector('a.next')?.getAttribute('href'),
listImages: images,
};
},
};
// == MangaDex =====================================================================================
var mangadex = {
name: 'MangaDex',
url: /https?:\/\/(www.)?mangadex.org\/chapter\/.+(\/.+)?/,
homepage: 'https://mangadex.org/',
language: ['English'],
category: 'manga',
waitEle: "a[href^='/chapter/']",
async run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const chapterId = W.location.pathname.match(/\/chapter\/([^/]+)(\/\d+)?/)[1];
const home = `https://api.mangadex.org/at-home/server/${chapterId}`;
const server = await fetch(home).then((res) => res.json());
const images = server.chapter.data;
const chapters = [...document.querySelectorAll("a[href^='/chapter/']")].map((a) => a.getAttribute('href'));
return {
title: document.querySelector('title')?.text.replace(' - MangaDex', ''),
series: document.querySelector("a.text-primary[href^='/title/']")?.getAttribute('href'),
pages: images.length,
prev: chapters[0] !== W.location.pathname ? chapters[0] : '#',
next: chapters[1] !== W.location.pathname ? chapters[1] : '#',
listImages: images.map((img) => `${server.baseUrl}/data/${server.chapter.hash}/${img}`),
};
},
};
// == MangaFox =====================================================================================
var mangafox = {
name: ['MangaFox', 'MangaHere'],
url: /https?:\/\/(www.)?(fanfox.net|mangahere.cc)\/manga\/.+\/.+\//,
homepage: ['https://fanfox.net/', 'https://www.mangahere.cc/'],
language: ['English'],
category: 'manga',
waitVar: 'chapterid',
async run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const key = document.querySelector('#dm5_key')?.getAttribute('value');
const options = {
method: 'GET',
headers: {
'Content-Type': 'text/plain',
},
};
const src = Array(W.imagecount)
.fill(0)
.map(async (_, i) => {
const url = `chapterfun.ashx?cid=${W.chapterid || W.chapter_id}&page=${i}&key=${key}`;
const api = await fetch(url, options).then((res) => res.text());
// eslint-disable-next-line no-eval
(0, eval)(api);
// @ts-ignore
return d;
});
const images = await Promise.all(src);
return {
title: document.querySelector('.reader-header-title div')?.textContent?.trim(),
series: document.querySelector('.reader-header-title a')?.getAttribute('href'),
pages: W.imagecount,
prev: W.prechapterurl,
next: W.nextchapterurl,
listImages: images.map((img, i) => img[i === 0 ? 0 : 1]),
};
},
};
// == MangaFreak ===================================================================================
var mangafreak = {
name: 'MangaFreak',
url: /https?:\/\/.{3,4}?(mangafreak).net\/Read.+/,
homepage: 'https://mangafreak.net/',
language: ['English'],
category: 'manga',
run() {
const chapter = document.querySelector('.chapter_list select option:checked');
const images = [...document.querySelectorAll('.mySlides img')];
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.title a')?.getAttribute('href'),
pages: images.length,
prev: chapter?.previousElementSibling?.getAttribute('value'),
next: chapter?.nextElementSibling?.getAttribute('value'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
var mangago = {
name: 'Mangago',
url: /https?:\/\/(www.)?mangago.me\/.*\/.*\/.*/,
homepage: 'https://www.mangago.me/',
language: ['English'],
category: 'manga',
waitVar: 'imgsrcs',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const key = CryptoJS.enc.Hex.parse('e11adc3949ba59abbe56e057f20f883e');
const iv = CryptoJS.enc.Hex.parse('1234567890abcdef1234567890abcdef');
const opinion = { iv, padding: CryptoJS.pad.ZeroPadding };
const images = CryptoJS.AES.decrypt(W.imgsrcs, key, opinion)
.toString(CryptoJS.enc.Utf8)
.split(',');
return {
title: `${W.manga_name} ${W.chapter_name}`,
series: W.mid,
pages: W.total_pages,
prev: document.querySelector('.recom p:nth-child(5) a')?.getAttribute('href'),
next: W.next_c_url,
listImages: images,
};
},
};
// == mangahosted
// ===================================================================================
var mangahosted = {
name: 'mangahosted',
url: /https?:\/\/(www.)?mangahosted.com\/manga\/.+\/.+/,
homepage: 'https://mangahosted.com/',
language: ['Portuguese'],
category: 'manga',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const images = [...document.querySelectorAll('picture img')];
return {
title: $('.breadcrumb li:eq(3)').text().trim(),
series: $('.breadcrumb li:eq(2) a').attr('href'),
pages: images.length,
prev: W.$read_prev,
next: W.$read_next,
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
// == MangaHub =====================================================================================
var mangahub = {
name: 'MangaHub',
url: /https?:\/\/(www.)?(mangahub).io\/chapter\/.+\/.+/,
homepage: 'https://mangahub.io/',
language: ['English'],
category: 'manga',
waitEle: '#select-chapter',
async run() {
function getCookie(name) {
const re = new RegExp(`${name}=([^;]+)`);
const value = re.exec(document.cookie);
return value != null ? decodeURIComponent(value[1]) : null;
}
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const slug = W.CURRENT_MANGA_SLUG || window.location.pathname.split('/')[2];
const number = window.location.pathname.split('/')[3].replace('chapter-', '');
const data = { query: `{chapter(x:m01,slug:"${slug}",number:${number}){pages}}` };
const options = {
method: 'POST',
body: JSON.stringify(data),
headers: {
'Content-Type': 'application/json',
'x-mhub-access': getCookie('mhub_access'),
},
};
const api = await fetch('https://api.mghubcdn.com/graphql', options).then((res) => res.json());
const images = Object.values(JSON.parse(api?.data.chapter.pages.toString()));
return {
title: document.querySelector('#mangareader h3')?.textContent?.trim(),
series: document.querySelector('#mangareader a')?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('.previous a')?.getAttribute('href'),
next: document.querySelector('.next a')?.getAttribute('href'),
listImages: images.map((i) => `https://img.mghubcdn.com/file/imghub/${i}`),
};
},
};
// == MangaKakalot =================================================================================
var mangakakalot = {
name: ['MangaKakalot', 'MangaNelo', 'MangaNato'],
url: /https?:\/\/(www.)?((manganelo|mangakakalot).com\/chapter\/.+\/.+|(manganato|readmanganato|chapmanganato).com\/manga-\w\w\d+\/chapter-\d+)/,
homepage: [
'https://mangakakalot.com/page',
'https://www.manganelo.com/',
'https://www.manganato.com/',
],
language: ['English'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('#vungdoc img, .container-chapter-reader img')];
return {
title: document
.querySelector('.info-top-chapter h2, .imageOptions-chapter-info-top h1, .panel-chapter-info-top h1')
?.textContent?.trim(),
series: document.querySelectorAll('span a[title]').item(1).getAttribute('href'),
pages: images.length,
prev: document.querySelector('.navi-change-chapter-btn-prev, .next')?.getAttribute('href'),
next: document.querySelector('.navi-change-chapter-btn-next, .back')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
var mangapark = {
name: 'MangaPark',
url: /https?:\/\/(www.)?mangapark.(com|me|org|net)\/(manga|chapter|comic)\/.+\/.+/,
homepage: 'https://mangapark.net/',
language: ['English'],
category: 'manga',
waitVar: 'CryptoJS',
run() {
const pass = JSON.parse(CryptoJS.AES.decrypt(amWord, amPass).toString(CryptoJS.enc.Utf8));
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.nav-title a')?.getAttribute('href'),
pages: imgPathLis.length,
prev: document.querySelector('.nav-prev a')?.getAttribute('href'),
next: document.querySelector('.nav-next a')?.getAttribute('href'),
listImages: imgPathLis.map((i, index) => `${imgCdnHost + i}?${pass[index]}`),
};
},
};
// == Mangareader ==================================================================================
var mangareader = {
name: 'Mangareader',
url: /https?:\/\/(www.)?mangareader.to\/read\/.+\/.+\/.+/,
homepage: 'https://mangareader.to',
language: ['English'],
category: 'manga',
obs: 'Some galleries will not be usable',
waitEle: '.ds-image, .iv-card',
run() {
const chapter = document.querySelector('.chapter-item.active');
const images = [
...document.querySelectorAll('.ds-image:not(.shuffled)[data-url], .iv-card:not(.shuffled)[data-url]'),
];
return {
title: document.querySelector('.hr-manga h2')?.textContent?.trim(),
series: document.querySelector('.hr-manga')?.getAttribute('href'),
pages: images.length,
prev: chapter?.nextElementSibling?.querySelector('a')?.getAttribute('href'),
next: chapter?.previousElementSibling?.querySelector('a')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('data-url')),
};
},
};
// == MangaSee =====================================================================================
var mangasee = {
name: ['MangaSee', 'Manga4life'],
url: /https?:\/\/(www.)?(mangasee123|manga4life).com\/read-online\/.+/,
homepage: ['https://mangasee123.com/', 'https://manga4life.com/'],
language: ['English'],
category: 'manga',
waitAttr: ['.img-fluid', 'src'],
run() {
const src = document.querySelector('.img-fluid')?.getAttribute('src') || '';
const script = [...document.querySelectorAll('body script:not([src])')].at(-1)?.textContent;
const textCurChapter = script?.match(/CurChapter = ({.+});/) || [];
const CurChapter = JSON.parse(textCurChapter[1]);
const textCHAPTERS = script?.match(/CHAPTERS = (\[\{.+}]);/) || [];
const CHAPTERS = JSON.parse(textCHAPTERS[1]);
const CurChapterIndex = CHAPTERS.findIndex((chap) => chap.Chapter === CurChapter.Chapter);
function ChapterURLEncode(reference) {
let ChapterString = CHAPTERS[CurChapterIndex + reference];
if (ChapterString === undefined) {
return '#';
}
ChapterString = ChapterString.Chapter;
let Index = '';
const IndexString = ChapterString.substring(0, 1);
if (IndexString !== '1') {
Index = `-index-${IndexString}`;
}
const Chapter = parseInt(ChapterString.slice(1, -1), 10);
let Odd = '';
const OddString = ChapterString[ChapterString.length - 1];
if (OddString !== '0') {
Odd = `.${OddString}`;
}
return window.location.href.replace(/-chapter-.+/, `-chapter-${Chapter}${Odd}${Index}.html`);
}
return {
title: document
.querySelector('title')
?.textContent?.replace(/ Page .+/, '')
.trim(),
series: document.querySelector('.MainContainer a')?.getAttribute('href'),
pages: parseInt(CurChapter.Page, 10),
prev: ChapterURLEncode(-1),
next: ChapterURLEncode(+1),
listImages: Array(parseInt(CurChapter.Page, 10))
.fill(0)
.map((_, i) => src.replace(/-\d\d\d.png/, `-${String(`000${i + 1}`).slice(-3)}.png`)),
};
},
};
// == MangaTigre ===================================================================================
var mangatigre = {
name: 'MangaTigre',
url: /https?:\/\/(www.)?mangatigre.net\/.+\/.+\/.+/,
homepage: 'https://www.mangatigre.net/',
language: ['Spanish'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('.chapter-content img')];
const chapter = document.querySelector('.form-control option:checked');
return {
title: document.querySelector('.page-title')?.textContent?.trim(),
series: document.querySelector('.breadcrumb li:nth-child(3) a')?.getAttribute('href'),
pages: images.length,
prev: chapter?.nextElementSibling?.getAttribute('value'),
next: chapter?.previousElementSibling?.getAttribute('value'),
listImages: images.map((img) => img.getAttribute('data-src') || img.getAttribute('src')),
};
},
};
// == MangaTown ====================================================================================
var mangatown = {
name: 'MangaTown',
url: /https?:\/\/(www.|m.)?mangatown.com\/manga\/.+\/.+/,
homepage: 'https://www.mangatown.com/',
language: ['English'],
category: 'manga',
waitVar: 'chapter_id',
async run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const key = document.querySelector('#dm5_key')?.getAttribute('value');
const options = {
method: 'GET',
headers: {
'Content-Type': 'text/plain',
},
};
const src = Array(W.total_pages)
.fill(0)
.map(async (_, i) => {
const url = `chapterfun.ashx?cid=${W.chapter_id}&page=${i}&key=${key}`;
const api = await fetch(url, options).then((res) => res.text());
// eslint-disable-next-line no-eval
(0, eval)(api);
// @ts-ignore
return d;
});
const images = await Promise.all(src);
const chapter = document.querySelector('#top_chapter_list option:checked');
return {
title: document.querySelector('.title h1')?.textContent,
series: W.series_url,
pages: images.length,
prev: chapter?.previousElementSibling?.getAttribute('value'),
next: chapter?.nextElementSibling?.getAttribute('value'),
listImages: images.map((img, i) => img[i === 0 ? 0 : 1]),
};
},
};
var manhuascan = {
name: 'ManhuaScan',
url: /https?:\/\/(www.)?manhuascan.io\/.+chapter.+/,
homepage: 'https://manhuascan.io/',
language: ['English'],
category: 'manga',
waitVar: 'imgsrcs',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const key = CryptoJS.enc.Hex.parse('e11adc3949ba59abbe56e057f20f131e');
const iv = CryptoJS.enc.Hex.parse('1234567890abcdef1234567890abcdef');
const opinion = { iv, padding: CryptoJS.pad.ZeroPadding };
const images = CryptoJS.AES.decrypt(W.imgsrcs, key, opinion)
.toString(CryptoJS.enc.Utf8)
.split(',');
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.breadcrumb li:nth-child(3) a')?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('.chapter-select a:first-of-type')?.getAttribute('href'),
next: document.querySelector('.chapter-select a:last-of-type')?.getAttribute('href'),
listImages: images,
};
},
};
// == MReader ======================================================================================
var mreader = {
name: 'MReader',
url: /https?:\/\/(www.)?mreader.co\/reader\/.*/,
homepage: 'https://www.mreader.co/',
language: ['English'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('#chapter-reader img')];
return {
title: document.querySelector('.titles')?.textContent?.trim(),
series: document.querySelector('.titles a')?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('.chnav.prev')?.getAttribute('href'),
next: document.querySelector('.chnav.next')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
// == NineManga ====================================================================================
var ninemanga = {
name: 'NineManga',
url: /https?:\/\/(www.)?ninemanga.com\/chapter\/.+\/.+\.html/,
homepage: 'https://ninemanga.com/',
language: ['English'],
category: 'manga',
run() {
const chapter = document.querySelector('#chapter option:checked');
const pages = [...document.querySelector('#page').querySelectorAll('option')];
return {
title: document.querySelector('.tip a')?.textContent?.trim(),
series: document.querySelector('.subgiude > li:nth-child(2) > a')?.getAttribute('href'),
pages: pages.length,
prev: chapter?.nextElementSibling?.getAttribute('value'),
next: chapter?.previousElementSibling?.getAttribute('value'),
listPages: pages.map((item) => $(item).val()),
img: '.manga_pic',
};
},
};
// == PandaManga ==================================================================================
var pandamanga = {
name: 'PandaManga',
url: /https?:\/\/(www.)?pandamanga.xyz\/.+\/.+\/.+/,
homepage: 'https://www.pandamanga.com/',
language: ['English'],
category: 'manga',
run() {
const chapter = document.querySelector('.select-chapter option:checked');
const data = JSON.parse(document.getElementById('__NEXT_DATA__').textContent);
const images = data.props.pageProps.mangaview.source
.split(',')
.filter((url) => url.length > 0);
return {
title: data.props.pageProps.mangaview.nameSeoChapter,
series: document.querySelector('.allc a')?.getAttribute('href'),
pages: images.length,
prev: chapter?.nextElementSibling?.getAttribute('value'),
next: chapter?.previousElementSibling?.getAttribute('value'),
listImages: images,
};
},
};
// == RawDevart ===================================================================================
var rawdevart = {
name: 'RawDevart',
url: /https?:\/\/(www.)?rawdevart.com\/comic\/.+\/.+\//,
homepage: 'https://rawdevart.com',
language: ['Raw'],
category: 'manga',
waitVar: 'rconfig',
waitEle: '#chapter-list select',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const chapter = document.querySelector('#chapter-list option:checked');
const images = [...document.querySelectorAll('#img-container img')];
return {
title: W.rconfig.chapterTitle,
series: W.rconfig.prefix,
pages: images.length,
prev: chapter?.nextElementSibling?.getAttribute('value'),
next: chapter?.previousElementSibling?.getAttribute('value'),
listImages: images.map((item) => $(item).attr('data-src') || $(item).attr('src')),
};
},
};
// == ReadComicsOnline =============================================================================
var readcomicsonline = {
name: 'ReadComicsOnline',
url: /https?:\/\/(www.)?readcomicsonline.ru\/comic\/.*\/\d*/,
homepage: 'https://readcomicsonline.ru/',
language: ['English'],
category: 'comic',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const images = [...document.querySelectorAll('#all img')];
return {
title: W.title.replace(/ - Page \d+/, ''),
series: document.querySelector('div.pager-cnt a')?.getAttribute('href'),
pages: W.pages.length,
prev: W.prev_chapter,
next: W.next_chapter,
listImages: images.map((img) => img.getAttribute('data-src')),
};
},
};
// == ReadManga.Today ==============================================================================
var readmangatoday = {
name: ['ReadManga Today', 'Funmanga', 'MangaDoom', 'MangaInn'],
url: /https?:\/\/(www.)?(funmanga|mngdoom|readmng|mangainn).(com|net)\/.+\/\d+/,
homepage: [
'https://www.readmng.com/',
'https://funmanga.com/',
'https://mngdoom.com/',
'https://www.mangainn.net/',
],
language: ['English'],
category: 'manga',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
return {
title: W.chapter_page_title,
series: W.manga_url,
pages: W.images.length,
prev: W.prev_chapter_url,
next: W.next_chapter_url,
listImages: W.images.map((item) => item.url),
};
},
};
// == ReaperScans ==================================================================================
var reaperscans = {
name: 'ReaperScans',
url: /https?:\/\/(www.)?reaperscans.com\/comics\/.+\/chapters\/.+/,
homepage: 'https://reaperscans.com/',
language: ['English'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('main img')];
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.fa-list')?.parentElement?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('.fa-arrow-left')?.parentElement?.getAttribute('href'),
next: document.querySelector('.fa-arrow-right')?.parentElement?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('data-src') || img.getAttribute('src')),
};
},
};
// == SenManga =====================================================================================
var senmanga = {
name: 'SenManga(Raw)',
url: /https?:\/\/raw.senmanga.com\/.+\/.+\/?/,
homepage: 'https://raw.senmanga.com/',
language: ['Raw'],
category: 'manga',
run() {
const url = `/${window.location.pathname.split('/')[1]}/${window.location.pathname.split('/')[2]}`;
const num = parseInt(document.querySelector('.page-list select option:last-child')?.getAttribute('value') || '0', 10);
const chapter = [...document.querySelectorAll('.dropdown-chapter li')];
const origin = chapter.findIndex((item) => item.querySelector('a')?.getAttribute('href') === window.location.href);
return {
title: $('.title').text().trim(),
series: document.querySelector('.breadcrumb li:nth-child(2) a')?.getAttribute('href'),
pages: num,
prev: chapter
.at(origin + 1)
?.querySelector('a')
?.getAttribute('href'),
next: chapter
.at(origin - 1)
?.querySelector('a')
?.getAttribute('href'),
listPages: Array(num)
.fill(0)
.map((_, i) => `${url}/${i + 1}/`),
img: '.picture',
};
},
};
// == ShimadaScans =================================================================================
var shimadascans = {
name: 'ShimadaScans',
url: /https?:\/\/(www.)?shimadascans.com\/.+series.+/,
homepage: 'https://shimadascans.com/',
language: ['English'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('.reading-content img')];
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.breadcrumb li:nth-child(2) a')?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('a.prev_page')?.getAttribute('href'),
next: document.querySelector('a.next_page')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
// == Tapas ======================================================================================
var tapas = {
name: 'KLManga',
url: /https?:\/\/(www.)?tapas.io\/episode\/.+/,
homepage: 'https://tapas.io/',
language: ['English'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('.viewer__body img.content__img')];
const chapter = document.querySelector('.js-episodes .body__item--selected');
return {
title: document.querySelector('.viewer__header .title')?.textContent?.trim(),
series: document.querySelector('.vw-nav__left a')?.getAttribute('href'),
pages: images.length,
prev: chapter?.previousElementSibling?.getAttribute('data-href'),
next: chapter?.nextElementSibling?.getAttribute('data-href'),
listImages: images.map((img) => img.getAttribute('data-src') || img.getAttribute('src')),
};
},
};
// == TenManga =====================================================================================
var tenmanga = {
name: 'TenManga',
url: /https?:\/\/(www.)?(tenmanga|gardenmanage).com\/(chapter|statuses)\/.+/,
homepage: 'https://www.tenmanga.com/',
language: ['English'],
category: 'manga',
waitVar: '_pageCtrl',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const chapter = document.querySelector('.mangaread-pagenav select option:checked');
// eslint-disable-next-line no-underscore-dangle
const images = W._pageCtrl.options.all_imgs_url;
return {
title: document.querySelector('.title h1')?.textContent?.trim(),
series: document.querySelector('.title a:nth-child(2)')?.getAttribute('href'),
pages: images.length,
prev: chapter?.nextElementSibling?.getAttribute('value'),
next: chapter?.previousElementSibling?.getAttribute('value'),
listImages: images,
};
},
};
// == TMOFans ==================================================================================
var tmofans = {
name: 'TuMangaOnline',
url: /https?:\/\/(www.)?(tmofans|lectortmo|followmanga).com\/.+\/.+\/(paginated|cascade)/,
homepage: 'https://lectortmo.com/',
language: ['Spanish'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('.img-container img')];
const pages = [
...document.querySelectorAll('div.container:nth-child(4) select#viewer-pages-select option'),
];
const num = images.length > 1 ? images.length : pages.length;
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('a[title="Volver"]')?.getAttribute('href'),
pages: num,
prev: document.querySelector('.chapter-prev a')?.getAttribute('href'),
next: document.querySelector('.chapter-next a')?.getAttribute('href'),
listPages: images.length > 1
? null
: Array(num)
.fill(0)
.map((_, i) => `${window.location.href.replace(/\/\d+$/, '')}/${i + 1}`),
listImages: images.length > 1 ? images.map((item) => $(item).attr('data-src')) : null,
img: '#viewer-container img, .viewer-page',
};
},
};
// == UnionMangas =================================================================================
var unionmangas = {
name: 'UnionMangas',
url: /https?:\/\/(www.)?unionleitor.top\/leitor\/.+\/.+/,
homepage: 'https://unionleitor.top/',
language: ['Portuguese'],
category: 'manga',
run() {
const chapter = document.querySelector('#capitulo_trocar option:checked');
const images = [...document.querySelectorAll('.img-manga')];
return {
title: document.querySelector('.titulo-leitura')?.textContent?.trim(),
series: document.querySelector('.breadcrumbs a:nth-child(3)')?.getAttribute('href'),
pages: images.length,
prev: chapter?.previousElementSibling?.getAttribute('value'),
next: chapter?.nextElementSibling?.getAttribute('value'),
listImages: images.map((img) => img.getAttribute('src')),
};
},
};
// == WebToons =====================================================================================
var webtoons = {
name: 'WebToons',
url: /https?:\/\/(www.)?webtoons.com\/.+viewer.+/,
homepage: 'https://www.webtoons.com/',
language: ['English'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('#_imageList img')];
return {
title: document.querySelector('.subj_info')?.textContent?.trim(),
series: document.querySelector('.subj_info a')?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('._prevEpisode')?.getAttribute('href'),
next: document.querySelector('._nextEpisode')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('data-src') || img.getAttribute('src')),
};
},
};
// == WPManga ======================================================================================
var wpmanga = {
name: ['Manga33'],
url: /https?:\/\/(www.)?(manga33).com\/manga\/.+/,
homepage: ['https://manga33.com/'],
language: ['English'],
category: 'manga',
run() {
const images = [...document.querySelectorAll('.chapter-content img')];
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.navbar-brand')?.getAttribute('href'),
pages: images.length,
prev: document.querySelector('a.prev')?.getAttribute('href'),
next: document.querySelector('a.next')?.getAttribute('href'),
listImages: images.map((img) => img.getAttribute('src')),
before() {
if (window.location.pathname.match(/all.html$/))
return;
if (window.location.pathname.match(/\d+-\d+.html$/)) {
window.location.pathname = window.location.pathname.replace(/-\d+.html$/, '-all.html');
}
},
};
},
};
// == ZeroScans ====================================================================================
var zeroscans = {
name: 'ZeroScans',
url: /https?:\/\/(www.)?zeroscans.com\/comics\/.+/,
homepage: 'https://zeroscans.com/',
language: ['English'],
category: 'manga',
waitVar: '__ZEROSCANS__',
run() {
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
// eslint-disable-next-line no-underscore-dangle
const images = W.__ZEROSCANS__.data.at(0).current_chapter.high_quality;
const chapters = document.querySelectorAll('.v-btn--router');
return {
title: document.querySelector('title')?.textContent?.trim(),
series: document.querySelector('.v-breadcrumbs li:nth-child(2) + a')?.getAttribute('href'),
pages: images.length,
prev: chapters[0]?.getAttribute('href'),
next: chapters[1]?.getAttribute('href'),
listImages: images,
};
},
};
/* eslint-disable no-unused-vars,@typescript-eslint/no-unused-vars */
const sites = [
asurascans,
batoto,
comicastle,
dysnatyscans,
flamecans,
inmanga,
klmanga,
leitor,
lhtranslation,
mangabuddy,
mangadex,
mangafox,
mangafreak,
mangago,
mangahosted,
mangahub,
mangakakalot,
mangapark,
mreader,
mangareader,
mangasee,
mangatigre,
mangatown,
manhuascan,
ninemanga,
pandamanga,
rawdevart,
readcomicsonline,
readmangatoday,
reaperscans,
senmanga,
shimadascans,
tapas,
tenmanga,
tmofans,
unionmangas,
webtoons,
wpmanga,
zeroscans,
foolslide,
madarawp, // Must be at the end because is a generic check
];
/* eslint-disable camelcase */
function logScript(...text) {
// eslint-disable-next-line no-console
console.log('MangaOnlineViewer: ', ...text);
return text;
}
// Replacement function for GM_listValues allowing for debugging in console
function getListGM() {
return typeof GM_listValues !== 'undefined' ? GM_listValues() : [];
}
// Replacement function for GM_listValues allowing for debugging in console
function removeValueGM(name) {
return typeof GM_deleteValue !== 'undefined'
? GM_deleteValue(name)
: logScript('Removing: ', name);
}
// Replacement function for GM_info allowing for debugging in console
const getInfoGM = typeof GM_info !== 'undefined'
? GM_info
: {
scriptHandler: 'Console',
script: {
name: 'Debug',
version: 'Testing',
},
};
// Replacement function for GM_getValue allowing for debugging in console
function getValueGM(name, defaultValue = null) {
if (typeof GM_getValue !== 'undefined') {
return GM_getValue(name, defaultValue);
}
logScript('Fake Getting: ', name, ' = ', defaultValue);
return defaultValue;
}
function getJsonGM(name, defaultValue = null) {
const result = getValueGM(name, defaultValue);
if (typeof result === 'string')
return JSON.parse(result);
return result;
}
function getSettings(defaultSettings) {
return getJsonGM('settings', defaultSettings);
}
// Replacement function for GM_setValue allowing for debugging in console
function setValueGM(name, value) {
try {
GM_setValue(name, value);
// logScript('Setting: ', name, ' = ', value as any);
return value.toString();
}
catch (e) {
logScript('Fake Setting: ', name, ' = ', value);
return String(value);
}
}
function setJsonGM(name, value) {
return setValueGM(name, JSON.stringify(value));
}
function setSettings(value) {
return setJsonGM('settings', value);
}
// See https://stackoverflow.com/a/2401861/331508 for optional browser sniffing code.
function getBrowser() {
const ua = navigator.userAgent;
let tem;
let M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*(\d+)/i) || [];
if (/trident/i.test(M[1])) {
tem = /\brv[ :]+(\d+)/g.exec(ua) || [];
return `IE ${tem[1] || ''}`;
}
if (M[1] === 'Chrome') {
tem = ua.match(/\b(OPR|Edge)\/(\d+)/);
if (tem !== null) {
return tem.slice(1).join(' ').replace('OPR', 'Opera');
}
}
M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?'];
tem = ua.match(/version\/(\d+)/i);
if (tem !== null) {
M.splice(1, 1, tem[1]);
}
return M.join(' ');
}
// See
// https://stackoverflow.com/questions/27487828/how-to-detect-if-a-userscript-is-installed-from-the-chrome-store
function getEngine() {
return getInfoGM.scriptHandler || 'Greasemonkey';
}
const isMobile = window.matchMedia('screen and (max-width: 768px)').matches;
/**
* Checks if a JavaScript value is empty
* @example
* isEmpty(null) // true
* isEmpty(undefined) // true
* isEmpty([]) // true
* isEmpty({}) // true
* isEmpty("") // true
* isEmpty(false) // false
* isEmpty(0) // false
* isEmpty([{},{"0":false},{"":0},{"0":0}]) // false
* isEmpty(42) // false
* isEmpty([{"":1},{"0":1}]) // false
* @param {any} value - item to test
* @returns {boolean} true if empty, otherwise false
*/
function isEmpty(value) {
return (value === null || // check for null
typeof value === 'undefined' ||
value === undefined || // check for undefined
(typeof value === 'string' && value === '') || // check for empty string
(Array.isArray(value) && value.length === 0) || // check for empty array
(typeof value === 'object' && Object.keys(value).length === 0));
}
/**
* Checks if value is nothing. Deep-checks arrays and objects
* @example
* isNothing(null) // true
* isNothing(undefined) // true
* isNothing([]) // true
* isNothing({}) // true
* isNothing("") // true
* isNothing(false) // true
* isNothing(0) // true
* isNothing([{},{"0":false},{"":0},{"0":0}]) // true
* isNothing(42) // false
* isNothing([{"":1},{"0":1}]) // false
* @param {any} value - item to test
* @returns {boolean} true if nothing, otherwise false
*/
function isNothing(value) {
const isEmptyObject = (a) => {
if (!Array.isArray(a)) {
// it's an Object, not an Array
const hasNonempty = Object.keys(a).some((element) => !isNothing(a[element]));
return hasNonempty ? false : isEmptyObject(Object.keys(a));
}
// check if array is really not empty as JS thinks at least one element should be non-empty
return !a.some((element) => !isNothing(element));
};
return (
// eslint-disable-next-line eqeqeq
value == false ||
value === 0 ||
isEmpty(value) ||
(typeof value === 'object' && isEmptyObject(value)));
}
const colors = {
dark: {
name: 'dark',
50: '#C1C2C5',
100: '#A6A7AB',
200: '#909296',
300: '#5c5f66',
400: '#373A40',
500: '#2C2E33',
600: '#25262b',
700: '#1A1B1E',
800: '#141517',
900: '#101113',
},
gray: {
name: 'gray',
50: '#f8f9fa',
100: '#f1f3f5',
200: '#e9ecef',
300: '#dee2e6',
400: '#ced4da',
500: '#adb5bd',
600: '#868e96',
700: '#495057',
800: '#343a40',
900: '#212529',
},
red: {
name: 'red',
50: '#fff5f5',
100: '#ffe3e3',
200: '#ffc9c9',
300: '#ffa8a8',
400: '#ff8787',
500: '#ff6b6b',
600: '#fa5252',
700: '#f03e3e',
800: '#e03131',
900: '#c92a2a',
},
pink: {
name: 'pink',
50: '#fff0f6',
100: '#ffdeeb',
200: '#fcc2d7',
300: '#faa2c1',
400: '#f783ac',
500: '#f06595',
600: '#e64980',
700: '#d6336c',
800: '#c2255c',
900: '#a61e4d',
},
grape: {
name: 'grape',
50: '#f8f0fc',
100: '#f3d9fa',
200: '#eebefa',
300: '#e599f7',
400: '#da77f2',
500: '#cc5de8',
600: '#be4bdb',
700: '#ae3ec9',
800: '#9c36b5',
900: '#862e9c',
},
violet: {
name: 'violet',
50: '#f3f0ff',
100: '#e5dbff',
200: '#d0bfff',
300: '#b197fc',
400: '#9775fa',
500: '#845ef7',
600: '#7950f2',
700: '#7048e8',
800: '#6741d9',
900: '#5f3dc4',
},
indigo: {
name: 'purple',
50: '#edf2ff',
100: '#dbe4ff',
200: '#bac8ff',
300: '#91a7ff',
400: '#748ffc',
500: '#5c7cfa',
600: '#4c6ef5',
700: '#4263eb',
800: '#3b5bdb',
900: '#364fc7',
},
blue: {
name: 'blue',
50: '#e7f5ff',
100: '#d0ebff',
200: '#a5d8ff',
300: '#74c0fc',
400: '#4dabf7',
500: '#339af0',
600: '#228be6',
700: '#1c7ed6',
800: '#1971c2',
900: '#1864ab',
},
cyan: {
name: 'cyan',
50: '#e3fafc',
100: '#c5f6fa',
200: '#99e9f2',
300: '#66d9e8',
400: '#3bc9db',
500: '#22b8cf',
600: '#15aabf',
700: '#1098ad',
800: '#0c8599',
900: '#0b7285',
},
teal: {
name: 'teal',
50: '#e6fcf5',
100: '#c3fae8',
200: '#96f2d7',
300: '#63e6be',
400: '#38d9a9',
500: '#20c997',
600: '#12b886',
700: '#0ca678',
800: '#099268',
900: '#087f5b',
},
green: {
name: 'green',
50: '#ebfbee',
100: '#d3f9d8',
200: '#b2f2bb',
300: '#8ce99a',
400: '#69db7c',
500: '#51cf66',
600: '#40c057',
700: '#37b24d',
800: '#2f9e44',
900: '#2b8a3e',
},
lime: {
name: 'lime',
50: '#f4fce3',
100: '#e9fac8',
200: '#d8f5a2',
300: '#c0eb75',
400: '#a9e34b',
500: '#94d82d',
600: '#82c91e',
700: '#74b816',
800: '#66a80f',
900: '#5c940d',
},
yellow: {
name: 'yellow',
50: '#fff9db',
100: '#fff3bf',
200: '#ffec99',
300: '#ffe066',
400: '#ffd43b',
500: '#fcc419',
600: '#fab005',
700: '#f59f00',
800: '#f08c00',
900: '#e67700',
},
orange: {
name: 'orange',
50: '#fff4e6',
100: '#ffe8cc',
200: '#ffd8a8',
300: '#ffc078',
400: '#ffa94d',
500: '#ff922b',
600: '#fd7e14',
700: '#f76707',
800: '#e8590c',
900: '#d9480f',
},
darkblue: {
name: 'darkblue',
50: '#E8F4F9',
100: '#D9DEE9',
200: '#B7C2DA',
300: '#6482C0',
400: '#4267B2',
500: '#385898',
600: '#314E89',
700: '#29487D',
800: '#223B67',
900: '#1E355B',
},
};
const darkest = 10;
const lightest = 95;
function setLightness(hsl, lightness) {
hsl.l = lightness;
return tinycolor(hsl).toHexString();
}
function getTextColor(hex) {
const color = tinycolor(hex);
const hsl = color.toHsl();
return setLightness(hsl, color.isDark() ? lightest : darkest);
}
function svgToUrl(str) {
const cleaned = str
.replace(/[\t\n\r]/gim, '') // Strip newlines and tabs
.replace(/\s\s+/g, ' ') // Condense multiple spaces
.replace(/'/gim, '\\i'); // Normalize quotes
const encoded = encodeURIComponent(cleaned)
.replace(/\(/g, '%28') // Encode brackets
.replace(/\)/g, '%29');
return `data:image/svg+xml;charset=UTF-8,${encoded}`;
}
Object.values(colors).map((i) => i['900']);
// Icons from https://tabler-icons.io/
// Icons for Navigation
const IconArrowBigRight = `
`;
const IconArrowBigLeft = `
`;
const IconFileDownload = `
`;
const IconLoader2 = `
`;
const IconCategory = `
`;
// Icons for Global Controls
const IconX = `
`;
const IconMenu2 = `
`;
const IconArrowAutofitWidth = `
`;
const IconArrowAutofitHeight = `
`;
const IconZoomInArea = `
`;
const IconZoomOutArea = `
`;
const IconZoomPan = `
`;
const IconArrowAutofitDown = `
`;
const IconArrowAutofitLeft = `
`;
const IconArrowAutofitRight = `
`;
const IconSpacingVertical = `
`;
const IconSettings = `
`;
const IconKeyboard = `
`;
const IconListNumbers = `
`;
const IconBookmarks = `
`;
const IconExternalLink = `
`;
const IconTrash = `
`;
const IconSun = `
`;
const IconMoon = `
`;
const IconCheck = `
`;
const IconPalette = `
`;
// Icons for Page Controls
const IconBookmark = `
`;
const IconBookmarkOff = `
`;
const IconEye = `
`;
const IconEyeOff = `
`;
const IconZoomCancel = `
`;
const IconZoomIn = `
`;
const IconZoomOut = `
`;
const IconRefresh = `
`;
const IconPhoto = `
`;
const IconPhotoOff = `
`;
// language=CSS
var cssStyles = `
:root,
.dark,
.dark .default,
[data-theme='dark'] {
--theme-body-background: ${colors.dark['600']};
--theme-body-text-color: ${colors.dark['50']};
--theme-text-color: ${colors.dark['50']};
--theme-primary-color: ${colors.dark['700']};
--theme-primary-text-color: ${colors.dark['50']};
--theme-background-color: ${colors.dark['600']};
--theme-hightlight-color: ${colors.dark['500']};
--theme-border-color: ${colors.dark['400']};
}
.light,
.light .default,
[data-theme='light'] {
--theme-body-background: ${colors.gray['50']};
--theme-body-text-color: ${colors.gray['900']};
--theme-text-color: ${colors.gray['900']};
--theme-primary-color: ${colors.gray['300']};
--theme-primary-text-color: ${colors.gray['900']};
--theme-background-color: ${colors.gray['50']};
--theme-hightlight-color: ${colors.gray['500']};
--theme-border-color: ${colors.gray['100']};
}
/* Simple Normalizer */
html {
font-size: 100%;
}
body {
margin: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 20px;
color: #333;
background-color: #fff;
padding: 0;
}
a,
a:link,
a:visited,
a:active,
a:focus {
color: var(--theme-body-text-color);
text-decoration: none;
}
img {
height: auto;
vertical-align: middle;
border: 0 none;
}
.icon-tabler {
height: 1rem;
width: 1rem;
align-self: center;
vertical-align: sub;
}
#nprogress .bar {
background: #29d;
position: fixed;
z-index: 1031;
top: 0;
left: 0;
width: 100%;
height: 4px;
}
#MangaOnlineViewer {
padding-bottom: 40px;
min-height: 760px;
min-width: 360px;
width: 100%;
height: 100%;
text-decoration: none;
color: var(--theme-body-text-color);
background-color: var(--theme-body-background);
}
#MangaOnlineViewer #Chapter {
display: grid;
grid-template-columns: repeat(1, 1fr);
min-width: 225px;
}
#MangaOnlineViewer #Chapter.FluidLTR {
direction: ltr;
}
#MangaOnlineViewer #Chapter.FluidRTL {
direction: rtl;
}
#MangaOnlineViewer #Chapter.FluidLTR,
#MangaOnlineViewer #Chapter.FluidRTL {
display: grid;
grid-template-columns: repeat(2, 1fr);
}
#MangaOnlineViewer #Chapter.FluidLTR .PageImg,
#MangaOnlineViewer #Chapter.FluidRTL .PageImg {
min-width: unset;
}
#MangaOnlineViewer #Chapter.FluidLTR .MangaPage.DoublePage,
#MangaOnlineViewer #Chapter.FluidRTL .MangaPage.DoublePage {
grid-column: span 2;
}
#MangaOnlineViewer #Chapter.FluidLTR .MangaPage:not(.DoublePage):nth-child(2n),
#MangaOnlineViewer #Chapter.FluidRTL .MangaPage:not(.DoublePage):nth-child(2n) {
justify-self: flex-start;
}
#MangaOnlineViewer #Chapter.FluidLTR .MangaPage:not(.DoublePage):nth-child(2n-1),
#MangaOnlineViewer #Chapter.FluidRTL .MangaPage:not(.DoublePage):nth-child(2n-1) {
justify-self: flex-end;
}
#MangaOnlineViewer #Chapter.Vertical .PageContent {
margin-bottom: 15px;
}
#MangaOnlineViewer #Chapter.FluidLTR .MangaPage,
#MangaOnlineViewer #Chapter.FluidRTL .MangaPage {
width: auto;
}
#MangaOnlineViewer #Chapter.FluidLTR .ZoomWidth .icon-tabler,
#MangaOnlineViewer #Chapter.FluidRTL .ZoomWidth .icon-tabler {
color: red;
}
#MangaOnlineViewer #SettingsPanel {
color: var(--theme-text-color);
padding: 10px;
position: fixed;
top: 0;
left: 0;
bottom: 0;
z-index: 1000;
transition: transform 0.3s ease-in, background-color 0.3s linear;
transform: translateX(-100%);
display: flex;
flex-flow: column;
gap: 5px;
overflow-y: auto;
max-width: 100vw;
width: 305px;
}
#MangaOnlineViewer #SettingsPanel.visible {
transform: translateX(0);
}
#MangaOnlineViewer #SettingsPanel .ControlLabel {
display: flex;
flex-flow: row wrap;
justify-content: space-between;
align-items: center;
}
#MangaOnlineViewer #SettingsPanel .ControlLabelItem {
display: flex;
align-items: center;
justify-content: space-between;
}
#MangaOnlineViewer #SettingsPanel .ControlLabelItem:not(.show) {
display: none;
}
#MangaOnlineViewer #ThemeSection {
border: 1px solid var(--theme-body-text-color);
border-radius: 10px;
padding: 10px;
}
#MangaOnlineViewer .closeButton {
width: fit-content;
height: fit-content;
position: absolute;
right: 10px;
top: 10px;
}
#MangaOnlineViewer .overlay {
position: fixed;
display: none;
width: 100%;
height: 100%;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 950;
cursor: pointer;
}
#MangaOnlineViewer .overlay.visible {
display: block;
}
#MangaOnlineViewer .ThemeRadio {
border: 1px solid var(--theme-text-color);
color: var(--theme-primary-text-color);
background-color: var(--theme-primary-color);
height: 20px;
width: 20px;
border-radius: 50%;
padding: 1px;
margin: 2px 5px;
position: relative;
}
#MangaOnlineViewer .ThemeRadio svg {
position: absolute;
top: 15%;
right: 15%;
}
#MangaOnlineViewer .ThemeRadio.custom {
/*background-image: url("${svgToUrl(IconPalette)}");*/
/*background-position: center;*/
/*background-repeat: no-repeat;*/
/*background-size: 80%;*/
}
#MangaOnlineViewer .ThemeRadio.selected .icon-tabler-check {
display: inline;
}
#MangaOnlineViewer .ThemeRadio:not(.selected) .icon-tabler-check {
display: none;
}
#MangaOnlineViewer #KeybindingsPanel {
padding: 8px;
position: fixed;
top: 65px;
right: 0;
transition: transform 0.3s ease-in-out;;
transform: translateX(100%);
line-height: 1.5em;
z-index: 1000;
}
#MangaOnlineViewer #KeybindingsPanel.visible {
transform: translateX(0);
display: block;
}
#MangaOnlineViewer #BookmarksPanel {
position: fixed;
top: 10%;
width: 50%;
left: 25%;
right: 25%;
text-align: center;
max-height: 70%;
transition: transform 0.3s ease-in-out;;
transform: scaleY(0%);
z-index: 1000;
}
#MangaOnlineViewer #BookmarksPanel.visible {
transform: scaleY(100%);
display: block;
}
#MangaOnlineViewer #BookmarksList {
padding: 0 15px;
overflow: auto;
max-height: 60vh;
}
#MangaOnlineViewer #BookmarksList .BookmarkItem {
display: flex;
flex-flow: row;
justify-content: space-between;
align-items: center;
padding: 2px;
}
#MangaOnlineViewer #BookmarksList .bookmarkData {
flex-basis: 15%;
}
#MangaOnlineViewer #BookmarksList .bookmarkURl {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
flex-basis: 55%;
}
#MangaOnlineViewer select {
height: 20px;
padding: 0;
margin-bottom: 5px;
}
#MangaOnlineViewer .ControlButton {
cursor: pointer;
border-radius: 5px;
border-width: 1px;
padding: 2px;
min-height: 32px;
color: var(--theme-primary-text-color);
background-color: var(--theme-primary-color);
border-color: var(--theme-border-color);
}
#MangaOnlineViewer .ControlButton:hover {
opacity: 0.8;
}
#MangaOnlineViewer .panel {
padding: 5px;
position: inherit;
border-radius: 5px;
background-color: var(--theme-background-color);
}
#MangaOnlineViewer .MangaPage {
width: 100%;
display: inline-block;
text-align: center;
/*transform: translate3d(0, 0, 0);*/
/*backface-visibility: hidden;*/
/*perspective: 1000px;*/
line-height: 0;
min-height: 22px;
min-width: 100%;
}
#MangaOnlineViewer .PageContent {
text-align: center;
display: inline-block;
overflow-x: auto;
max-width: 100%;
transition: all 0.3s ease-in-out;
height: 100%;
overflow-y: hidden;
}
#MangaOnlineViewer .MangaPage.hide .PageContent {
/*display: none;*/
height: 0;
}
#MangaOnlineViewer .MangaPage.hide .PageFunctions {
/*position:relative;*/
}
#MangaOnlineViewer .PageContent .PageImg[src=""],
#MangaOnlineViewer .PageContent .PageImg:not([src]) {
width: 40vw;
height: 80vh;
display: inline-block;
background-image: url("${svgToUrl(IconPhoto)}");
background-position: center;
background-repeat: no-repeat;
background-size: 20%;
background-color: var(--theme-hightlight-color);
}
#MangaOnlineViewer .PageContent .PageImg.imgBroken {
width: 40vw;
height: 80vh;
display: inline-block;
background-image: url("${svgToUrl(IconPhotoOff)}");
background-position: center;
background-repeat: no-repeat;
background-size: 20%;
background-color: var(--theme-hightlight-color);
}
#MangaOnlineViewer .Thumbnail .ThumbnailImg[src=""],
#MangaOnlineViewer .Thumbnail .ThumbnailImg:not([src]) {
width: 100px;
height: 150px;
display: inline-block;
background-image: url("${svgToUrl(IconPhoto)}");
background-position: center;
background-repeat: no-repeat;
background-size: 20%;
}
#MangaOnlineViewer .fitWidthIfOversize .PageContent .PageImg {
max-width: 100%;
}
#MangaOnlineViewer #gotoPage {
min-width: 35px;
}
#MangaOnlineViewer #ThemeSelector {
width: 110px;
}
#MangaOnlineViewer #Header {
display: flex;
justify-content: space-between;
align-items: center;
flex-flow: row nowrap;
transition: transform 0.3s ease-in;
position: sticky;
top: 0;
left: 0;
right: 0;
background-color: inherit;
z-index: 900;
}
#MangaOnlineViewer #Header.scroll.headroom-hide {
transform: translateY(-100%);
}
#MangaOnlineViewer #Header.scroll.headroom-show {
transform: translateY(-1%);
}
#MangaOnlineViewer #Header.hover,
#MangaOnlineViewer #Header.fixed,
#MangaOnlineViewer #Header.click {
position: static;
transform: none;
}
#MangaOnlineViewer #Header.headroom-end,
#MangaOnlineViewer #Header.visible,
#MangaOnlineViewer #Header.fixed {
transform: translateY(-1%);
position: sticky;
}
#MangaOnlineViewer #Header.hover:hover,
#MangaOnlineViewer #Header.fixed {
position: sticky;
}
#MangaOnlineViewer #Header.scroll #menu,
#MangaOnlineViewer #Header.fixed #menu,
#MangaOnlineViewer #Header.hover:hover #menu,
#MangaOnlineViewer #Header:not(.click).visible #menu {
display: none;
}
#MangaOnlineViewer #menu {
position: fixed;
min-height: 70px;
width: 100%;
top: 0;
z-index: 1;
color: var(--theme-body-text-color);
}
#MangaOnlineViewer #Header.click #menu {
cursor: pointer;
}
#MangaOnlineViewer #Header.click.visible #menu {
position: static;
width: 50px;
min-height: unset;
}
#MangaOnlineViewer #MangaTitle {
padding: 2px;
margin: 0;
font-size: 1.2rem;
font-weight: normal;
}
#MangaOnlineViewer #GlobalFunctions {
display: flex;
gap: 3px;
padding-left: 10px;
flex-wrap: wrap;
width: 300px;
z-index: 100;
}
#MangaOnlineViewer #GlobalFunctions .icon-tabler {
width: 25px;
height: 25px;
}
#MangaOnlineViewer #ChapterNavigation {
display: flex;
flex-flow: column nowrap;
justify-content: center;
align-items: end;
padding-right: 10px;
width: 300px;
}
#MangaOnlineViewer .ChapterControl {
display: flex;
flex-wrap: nowrap;
}
#MangaOnlineViewer .ChapterControl .NavigationControlButton {
display: inline-flex;
margin-left: 3px;
justify-content: center;
align-items: center;
padding: 5px 10px;
gap: 0.5em;
}
#MangaOnlineViewer .ChapterControl .NavigationControlButton .icon-tabler {
flex-shrink: 0;
align-self: center;
width: 1rem;
height: 1rem;
}
#MangaOnlineViewer .ChapterControl .NavigationControlButton[value='#'],
#MangaOnlineViewer .ChapterControl .NavigationControlButton[value=''],
#MangaOnlineViewer .ChapterControl .NavigationControlButton[value='undefined'] {
visibility: hidden;
}
#MangaOnlineViewer .ChapterControl #download.loading {
cursor: not-allowed;
pointer-events: none;
opacity: 0.6;
}
#MangaOnlineViewer .ChapterControl #download.disabled {
visibility: hidden;
}
#MangaOnlineViewer .ViewerTitle {
text-align: center;
min-height: 60px;
/*max-width: 500px;*/
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
padding: 5px;
flex-basis: 60%;
}
#MangaOnlineViewer .ViewerTitle #series[href='#'],
#MangaOnlineViewer .ViewerTitle #series[href=''],
#MangaOnlineViewer .ViewerTitle #series[href='undefined'] {
visibility: hidden;
}
#MangaOnlineViewer #Counters {
}
#MangaOnlineViewer .PageFunctions {
font-family: monospace;
display: flex;
justify-content: flex-end;
align-items: center;
margin: 0;
padding: 0;
gap: 3px;
position: absolute;
right: 0;
}
#MangaOnlineViewer .PageFunctions > .PageIndex {
background-color: var(--theme-primary-color);
color: var(--theme-primary-text-color);
min-width: 20px;
text-align: center;
display: inline-block;
padding: 3px 5px;
line-height: 1rem;
border-radius: 5px;
border-top-right-radius: 5px;
border-top-right-radius: 0;
}
#MangaOnlineViewer .PageFunctions .ControlButton {
padding: 3px;
display: flex;
justify-content: center;
align-items: center;
margin: 0;
border-width: 0;
min-height: auto;
opacity: 0.5;
}
#MangaOnlineViewer .PageFunctions:hover .ControlButton {
opacity: 1;
}
#MangaOnlineViewer .PageFunctions .ControlButton:hover {
opacity: 0.9;
}
#MangaOnlineViewer.light #ColorScheme > :not(.inverse),
#MangaOnlineViewer:not(.light) #ColorScheme > .inverse,
#MangaOnlineViewer .ChapterControl #download.loading > :not(.inverse),
#MangaOnlineViewer .ChapterControl #download:not(.loading) > .inverse,
#MangaOnlineViewer .MangaPage.hide .ControlButton.Hide > .inverse,
#MangaOnlineViewer .MangaPage:not(.hide) .ControlButton.Hide > :not(.inverse),
#MangaOnlineViewer.bookmarked .ControlButton.Bookmark > :not(.inverse),
#MangaOnlineViewer:not(.bookmarked) .ControlButton.Bookmark > .inverse {
display: none;
}
#MangaOnlineViewer.hideControls .PageFunctions {
visibility: hidden;
}
#MangaOnlineViewer #NavigationCounters {
margin: 5px;
width: 100%;
line-height: 1rem;
}
#MangaOnlineViewer #Navigation {
color: var(--theme-text-color);
background-color: var(--theme-hightlight-color);
bottom: -180px;
height: 185px;
overflow-x: hidden;
overflow-y: hidden;
padding-bottom: 20px;
position: fixed;
white-space: nowrap;
width: 100%;
text-align: center;
transition: transform 0.3s ease-in, background-color 0.3s linear;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
line-height: 0rem;
}
#MangaOnlineViewer #Navigation #Thumbnails {
overflow-x: auto;
overflow-y: hidden;
margin-right: 10px;
}
#MangaOnlineViewer #Navigation:hover {
transform: translateY(-180px);
}
#MangaOnlineViewer #Navigation.disabled {
display: none;
}
#MangaOnlineViewer #Navigation.visible {
transform: translateY(-180px);
}
#MangaOnlineViewer #Navigation .Thumbnail {
display: inline-block;
height: 150px;
margin: 0 5px;
border: 1px solid var(--theme-primary-color);
}
#MangaOnlineViewer #Navigation .Thumbnail .ThumbnailIndex {
color: var(--theme-text-color);
background-color: var(--theme-hightlight-color);
display: block;
opacity: 0.8;
position: relative;
bottom: 25%;
width: 100%;
line-height: 1rem;
}
#MangaOnlineViewer #Navigation .Thumbnail .ThumbnailImg {
cursor: pointer;
display: inline-block;
max-height: 150px;
min-height: 150px;
min-width: 80px;
max-width: 160px;
}
#MangaOnlineViewer #menu .icon-tabler {
position: absolute;
top: 5px;
left: 10px;
height: 32px;
width: 32px;
}
@media (max-width: 992px) {
#MangaOnlineViewer #Header {
flex-direction: column;
}
#MangaOnlineViewer .PageContent .PageImg {
max-width: 100%;
}
#MangaOnlineViewer .ViewerTitle {
order: 1;
min-height: auto;
padding: 0px;
margin: 0px;
}
#MangaOnlineViewer #GlobalFunctions {
flex-wrap: nowrap;
padding: 0;
width: auto;
order: 3;
padding: 5px;
}
#MangaOnlineViewer #ChapterNavigation {
order: 2;
}
#MangaOnlineViewer #GlobalFunctions #keybindings {
display: none;
}
}
/* Small devices (landscape phones) */
@media (max-width: 600px) {
#MangaOnlineViewer #Header {
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
}
#MangaOnlineViewer .ViewerTitle {
order: 1;
flex-basis: 100%;
margin-top: 0px;
}
#MangaOnlineViewer #GlobalFunctions {
order: 2;
padding: 0;
}
#MangaOnlineViewer #ChapterNavigation {
order: 3;
width: auto;
}
#MangaOnlineViewer #Navigation {
display: none;
}
#MangaOnlineViewer .PageFunctions {
padding: 0;
}
#MangaOnlineViewer .PageFunctions .ControlButton:not(.Bookmark) {
display: none;
}
#MangaOnlineViewer .PageFunctions .ControlButton.Bookmark {
opacity: 1;
}
#MangaOnlineViewer .PageContent {
margin: 0;
width: 100%;
}
#MangaOnlineViewer .PageContent .PageImg {
max-width: 100%;
}
#MangaOnlineViewer #GlobalFunctions .ControlButton:not(#settings) {
display: none;
}
#MangaOnlineViewer #GlobalFunctions {
min-width: auto;
}
#MangaOnlineViewer #SettingsPanel .DefaultZoom,
#MangaOnlineViewer #SettingsPanel .viewMode,
#MangaOnlineViewer #SettingsPanel .fitIfOversize,
#MangaOnlineViewer #SettingsPanel .showThumbnails,
#MangaOnlineViewer #SettingsPanel .lazyLoadImages,
#MangaOnlineViewer #SettingsPanel .downloadZip,
#MangaOnlineViewer #SettingsPanel .minZoom,
#MangaOnlineViewer #SettingsPanel .zoomStep,
#MangaOnlineViewer #SettingsPanel .headerType {
display: none;
}
#MangaOnlineViewer #KeybindingsPanel {
display: none;
}
#MangaOnlineViewer .ViewerTitle {
height: auto;
padding: 0;
}
#MangaOnlineViewer .ChapterControl {
}
#MangaOnlineViewer .ChapterControl .download {
display: none;
}
#MangaOnlineViewer #Counters {
display: none;
}
}
@-webkit-keyframes spin {
to {
transform: rotate(360deg)
}
}
@keyframes spin {
to {
transform: rotate(360deg)
}
}
.animate-spin {
-webkit-animation: spin 1s linear infinite;
animation: spin 1s linear infinite
}
@-webkit-keyframes spin-reverse {
0% {
transform: rotate(360deg)
}
to {
transform: rotate(0)
}
}
@keyframes spin-reverse {
0% {
transform: rotate(360deg)
}
to {
transform: rotate(0)
}
}
.animate-spin-reverse {
-webkit-animation: spin-reverse 1s linear infinite;
animation: spin-reverse 1s linear infinite
}
`;
// language=CSS
const startButton = `
#StartMOV {
font-size: 20px;
font-weight: bold;
color: #fff;
cursor: pointer;
margin: 20px;
padding: 10px 20px;
text-align: center;
border: none;
background-size: 300% 100%;
border-radius: 50px;
transition: all 0.4s ease-in-out;
background-image: linear-gradient(to right, #667eea, #764ba2, #6b8dd6, #8e37d7);
box-shadow: 0 4px 15px 0 rgba(116, 79, 168, 0.75);
position: fixed;
top: 10px;
right: 10px;
z-index: 10000;
}
#StartMOV:hover {
background-position: 100% 0;
transition: all 0.4s ease-in-out;
}
#StartMOV:focus {
outline: none;
}
`;
/**
* Deep diff between two object, using lodash
*
* We are comparing two objects: a and b.
* Object b is newer and similar to a.
* We are looking for changes from a to b.
* We assume that data types haven't changed (String to Number).
* We assume that parent is either an Array or an Object.
*
* This is the "easier on the programmer, harder on the computer" method.
* It's very inefficient. Worst case is something like O(n^n).
* But this works, is easier to understand that the "correct"
* implementation, and entertained me for a bit. *
*
* http://stackoverflow.com/questions/31295545/how-to-get-only-the-changed-values-from-two-json-objects
* https://gist.github.com/Yimiprod/7ee176597fef230d1451
* https://stackoverflow.com/questions/8572826/generic-deep-diff-between-two-objects
* https://qastack.com.br/programming/31683075/how-to-do-a-deep-comparison-between-2-objects-with-lodash
* @param {Object} changed Object compared
* @param {Object} original Object to compare with
* @return {Object} Return a new object who represent the diff
*/
const diffObj = (changed, original) => {
const changes = (object, base) => _.transform(object, (result, value, key) => {
if (!_.isEqual(value, base[key])) {
if (_.isArray(value)) {
result[key] = _.difference(value, base[key]);
}
else if (_.isObject(value) && _.isObject(base[key])) {
result[key] = changes(value, base[key]);
}
else {
result[key] = value;
}
}
});
return changes(changed, original);
};
var en_US = {
ID: 'en_US',
NAME: 'English (US)',
STARTING: 'Starting
MangaOnlineViewer',
RESUME: 'Resuming reading from Page ',
WAITING: 'Please wait, 3 seconds...',
CHOOSE_BEGINNING: 'Choose the Page to start from:',
BUTTON_START: 'Start MangaOnlineViewer',
SETTINGS: 'Settings',
LANGUAGE: 'Language',
COLOR_SCHEME: 'Color Scheme',
THEME: 'Theme',
THEME_HUE: 'Theme Primary Color Hue',
THEME_SHADE: 'Theme Primary Color Shade',
DEFAULT_LOAD_MODE: 'Default Load Mode',
LOAD_MODE_NORMAL: 'Normal(Wait 3 sec)',
LOAD_MODE_ALWAYS: 'Always(Immediately)',
LOAD_MODE_NEVER: 'Never(Manually)',
LOAD_SPEED: 'Load Speed Pages/Second',
DEFAULT_ZOOM: 'Default Zoom',
MINIMUM_ZOOM: 'Minimum Zoom relative to the width of screen (between 30 and 100)',
ZOOM_STEP: 'Zoom Change Step (between 5 and 50)',
DEFAULT_VIEW_MODE: 'Default View Mode',
VIEW_MODE_VERTICAL: 'Vertical',
VIEW_MODE_LEFT: 'Left to Right',
VIEW_MODE_RIGHT: 'Right to Left',
VIEW_MODE_WEBCOMIC: 'WebComic',
FIT_WIDTH_OVERSIZED: 'Fit Width if Oversized',
SHOW_THUMBNAILS: 'Show Thumbnails',
HIDE_CONTROLS: 'Always Hide Page Controls',
HEADER_TYPE: 'Change Header Type',
HEADER_HOVER: 'Hover',
HEADER_SCROLL: 'Scroll',
HEADER_CLICK: 'Click',
HEADER_FIXED: 'Fixed',
BUTTON_DOWNLOAD: 'Download',
DOWNLOAD_ZIP: 'Download Zip file',
DOWNLOAD_IMAGES: 'Download Images as Zip Automatically',
BUTTON_NEXT: 'Next',
NEXT_CHAPTER: 'Next Chapter',
BUTTON_PREVIOUS: 'Previous',
PREVIOUS_CHAPTER: 'Previous Chapter',
BOOKMARKS: 'Bookmarks',
BOOKMARK: 'Bookmark',
BOOKMARK_REMOVED: 'Bookmark Removed',
BOOKMARK_SAVED: 'Bookmark Saved',
BOOKMARK_MESSAGE: 'Next time you open this chapter it will resume from: