Greasy Fork

Novel Sites Enhance

Kakuyomu / Narou / Alphapolis auto bookmark & cheering, hightlight author & unreads, enhance history, download as txt

目前为 2024-06-03 提交的版本。查看 最新版本

// ==UserScript==
// @name           Novel Sites Enhance
// @name:ja        小説サイト機能強化
// @namespace      https://greasyfork.org/en/users/1264733
// @version        2024-06-03
// @description    Kakuyomu / Narou / Alphapolis auto bookmark & cheering, hightlight author & unreads, enhance history, download as txt
// @description:ja アルファポリス・カクヨム・なろう 自動しおり、自動応援、ハイライト著者と未読小説、強化閲覧履歴、TXTダウンロード。
// @author         LE37
// @license        MIT
// @include        /^https:\/\/kakuyomu\.jp\/my\/antenna\/reading_histories/
// @include        /^https:\/\/kakuyomu\.jp\/my\/antenna\/works/
// @include        /^https:\/\/kakuyomu\.jp\/works\/[0-9]+$/
// @include        /^https:\/\/kakuyomu\.jp\/works\/[0-9]+\/episodes\/[^\/]+$/
// @include        /^https:\/\/syosetu\.com\/favnovelmain\/list\//
// @include        /^https:\/\/ncode\.syosetu\.com\/[A-z0-9]+\/?$/
// @include        /^https:\/\/ncode\.syosetu\.com\/[A-z0-9]+\/[0-9]+\/?$/
// @include        /^https:\/\/yomou\.syosetu\.com\/rireki\/list\/$/
// @include        /^https:\/\/www\.alphapolis\.co\.jp\/mypage\/notification\/index\/110000/
// @include        /^https:\/\/www\.alphapolis\.co\.jp\/novel\/[0-9]+/[0-9]+/episode/[0-9]+$/
// @grant          GM_getValue
// @grant          GM_setValue
// @grant          GM_registerMenuCommand
// ==/UserScript==

(()=>{
	'use strict';

	let gMk;
	switch (location.host) {
		case "kakuyomu.jp":
			gMk = "HUN_K";
			break;
		case "ncode.syosetu.com":
		case "syosetu.com":
		case "yomou.syosetu.com":
			gMk = "HUN_N";
			break;
		case "www.alphapolis.co.jp":
			gMk = "HUN_A";
			break;
	}
	// GM menu
	GM_registerMenuCommand("CCheer", CCR);
	GM_registerMenuCommand("DAllEP", DAS);
	GM_registerMenuCommand("Author", ADA);
	GM_registerMenuCommand("Unread", SUN);
	GM_registerMenuCommand("Colour", SUC);
	// Read list
	const URD = GM_getValue(gMk);
	let tlo = URD ? URD : { ATC: false, SDB: false, FAC: "indigo", FCC: "orange", FUC: "red", FAU: 3, FAL:[], RRK:{} };
	let atc = tlo.ATC ? tlo.ATC : false;
	let sdb = tlo.SDB ? tlo.SDB : false;
	let tac = tlo.FAC ? tlo.FAC : "red";
	let tcc = tlo.FCC ? tlo.FCC : "deepskyblue";
	let tuc = tlo.FUC ? tlo.FUC : "orange";
	let tau = tlo.FAU;
	let rrk = tlo.RRK ? tlo.RRK : {};
	const tal = tlo.FAL;
	// Save list
	function USV() {
		tlo = { ATC: atc, SDB:sdb, FAC: tac, FCC: tcc, FUC: tuc, FAU: tau, FAL:tal, RRK:rrk };
		GM_setValue(gMk, tlo);
	}
	// Set fav author
	let sFa = false;
	// Set fav colour
	let sFc = false;
	let fAuthor;
	const uRi = location.href;
	const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

	switch (true) {
		case uRi.includes("/my/"):
		case uRi.includes("/favnovelmain/"):
		case uRi.includes("/mypage/"):
			FAV();
			CBT();
			CMU();
			if (gMk === "HUN_K") {
				const nologin = document.querySelector("header.isGuestUser") ? true : false;
				const pEle = nologin && uRi.includes("works") ? "ul.widget-antennaGuestList" : "ul.widget-antennaList";
				CRH(pEle, pEle + ">li:first-child", "https://kakuyomu.jp/works/", "/episodes/");
				if (!nologin) {
					CIB("main h1", "widget-antennaList-item", "h4.widget-antennaList-title", "a.widget-antennaList-continueReading");
				}
			} else if (gMk === "HUN_N") {
				CRH("div.c-up-page-title", "h2.c-up-page-title__text", "https://ncode.syosetu.com/", "/");
				CIB("div.c-up-filter", "p-up-bookmark-item", "div.p-up-bookmark-item__title>a", "a.c-button--sm");
			}
			break;
		case /^https:\/\/ncode\.syosetu\.com\/[A-z0-9]+\/?$/.test(uRi): {
			if(document.getElementById("novel_honbun")) {
				EPI();
			} else {
				// Add resume button
				const cKey = location.pathname.split("/")[1];
				if(Object.hasOwn(rrk, cKey)) {
					CRB(cKey, "p.novel_title", "https://ncode.syosetu.com/", "/");
				}
				// Add download all episodes button
				DAB(document.title, document.querySelector("ul.undernavi li:nth-child(2) a").href, 'select[name="no"]', "div.novel_writername");
			}
			break;
		}
		case /^https:\/\/yomou\.syosetu\.com\/rireki\/list\/$/.test(uRi):
			CRH("div.c-page-title", "h2.c-page-title__text", "https://ncode.syosetu.com/", "/");
			CIB("div.c-page-title", "p-rireki-item", "a.c-card__title", "div.p-rireki-item__button-link>a.c-button");
			break;
		case /^https:\/\/kakuyomu\.jp\/works\/[0-9]+$/.test(uRi): {
			// Add download all episodes button
			DAB(document.title.split("(")[0], uRi, "__NEXT_DATA__", "div.partialGiftWidgetActivityName");
			// Add resume button if nologin
			const cKey = location.pathname.split("/")[2];
			if ( document.querySelector('li[class^="GlobalHeaderGuestLink"]')
			&& Object.hasOwn(rrk, cKey)
			&& rrk[cKey].epi.length > 4) {
				CRB(cKey, "a[title]", "https://kakuyomu.jp/works/", "/episodes/");
			}
			break;
		}
		default:
			EPI();
	}

	// Episode page
	function EPI() {
		// eCheer button;
		let eCb;
		let rMf = false;
		switch (gMk) {
			case "HUN_K": {
				eCb = document.getElementById("episodeFooter-action-cheerButton");
				if ( document.getElementById("episodeFooter-action-cheerButton-cheer").classList.contains("isShown")
					&& tal.some(name => document.title.includes('(' + name + ') -')) ) {
					rMf = true;
				}
				// Custom reading history
				ANH(location.pathname.split("/")[2], location.pathname.split("/")[4], document.querySelector("h1.js-vertical-composition-item>a").title);
				// Button download as txt
				const title = document.querySelector("p.widget-episodeTitle")
								? document.querySelector("p.widget-episodeTitle").textContent
								: document.title.replace(/\s/g,"").match(/[^-]+/);
				TXT("div.widget-episodeBody", "div#episodeFooter-action-cheerButtons", title);
				break;
			}
			case "HUN_N": {
				eCb = document.querySelector("a.js-novelgood_change");
				if ( document.querySelector("div.is-empty")
					&& tal.some(name => document.querySelector('div.contents1 a:nth-child(2)').textContent.includes(name)) ) {
					rMf = true;
				}
				// Auto siori/bookmark
				if (document.querySelector("li.bookmark_now")) {
					sleep(Math.floor((Math.random() * (5000 - 2000 + 1)) + 2000)).then(() => {
						document.querySelector("li.bookmark_now>a").click();
					});
				}
				// Custom reading history
				ANH(location.pathname.split("/")[1], location.pathname.split("/")[2], document.title.split(" - ")[0]);
				// Button download as txt
				const title = document.querySelector("p.novel_subtitle")
								? document.querySelector("p.novel_subtitle").textContent
								: document.title.split("-")[1];
				TXT("div#novel_honbun", "div.center", title);
				break;
			}
			case "HUN_A":
				eCb = document.getElementById("contentMangaLikeBtnCircle");
				if ( !eCb.classList.contains("max")
					&& tal.some(name => uRi.includes(name)) ) {
					rMf = true;
				}
				break;
		}
		const ioc = new IntersectionObserver((entries) => {
			if (entries[0].intersectionRatio <= 0) return;
			ioc.disconnect();
			if (rMf) {
				eCb.style.backgroundColor = tcc;
				if (atc) {
					if (gMk === "HUN_A") {
						// Randomnumber = Math.floor(Math.random() * (maximum - minimum + 1)) + minimum;
						let x = 0;
						setInterval(function() {
							if (x < parseInt(eCb.getAttribute("data-content-like-limit"))) {
								//console.log(x);
								eCb.click();
							} else {
								return;
							}
							x++;
						}, Math.floor(Math.random() * (1000 - 500 + 1)) + 500);
					} else {
						eCb.click();
					}
					//console.log("===いいね===");
					sleep(1500).then(() => {
						if (gMk === "HUN_K" && !document.getElementById("episodeFooter-action-cheerButton-cheer").classList.contains("isShown")
						|| gMk === "HUN_N" && !document.querySelector("div.is-empty")
						|| gMk === "HUN_A" && document.getElementById("contentMangaLikeBtnCircle").classList.contains("max") ) {
							eCb.style.backgroundColor = "";
						}
					});
				}
			}
		});
		ioc.observe(eCb);
	}

	// Favorite page
	function FAV() {
		let fNode, fUnreadCount;
		switch (gMk) {
			case "HUN_K":
				fAuthor = "p.widget-antennaList-author";
				fNode = "li.widget-antennaList-item";
				fUnreadCount = "li.widget-antennaList-unreadEpisodeCount";
				break;
			case "HUN_N":
				fAuthor = "div.p-up-bookmark-item__author>a";
				fNode = "li.p-up-bookmark-item";
				fUnreadCount = "span.p-up-bookmark-item__unread-num";
				break;
			case "HUN_A":
				fAuthor = "h2.title>a";
				fNode = "div.content-main";
				fUnreadCount = "a.disp-order";
				break;
		}
		const tNode = document.querySelectorAll(fNode);
		for(let i = 0; i < tNode.length; i++) {
			const fAuthorTag = tNode[i].querySelector(fAuthor);
			const fAuthorName = (gMk === "HUN_A")
									? fAuthorTag.href.match(/\d+$/)[0]
									: fAuthorTag.textContent;
			fAuthorTag.style.color = CHK(fAuthorTag, fAuthorName)
										? tac
										: "";
			const tUnreadCount = tNode[i].querySelector(fUnreadCount);
			const fCurrent = (gMk === "HUN_A") && tUnreadCount
								? parseInt(tUnreadCount.textContent.match(/[0-9]+/)[0])
								: 0;
			let tUnreadNum;
			if (tUnreadCount) {
				tUnreadNum = (gMk === "HUN_K") ? parseInt(tUnreadCount.textContent.match(/[0-9]+/)[0])
								: (gMk === "HUN_N") ? parseInt(tUnreadCount.textContent)
								: parseInt(tNode[i].querySelector("a.total").textContent.match(/[0-9]+/)[0]) - fCurrent;
			} else {
				tUnreadNum = 0;
			}
			const fUnreadColor = CHK(fAuthorTag, fAuthorName) ? tac
									: tUnreadCount && tUnreadNum > tau ? tuc
									: "";
			if (tUnreadCount) {
				if (gMk === "HUN_K") {
					tNode[i].querySelector("a.widget-antennaList-continueReading").style.color = fUnreadColor;
				} else if (gMk === "HUN_N") {
					tNode[i].querySelector("span.p-up-bookmark-item__unread").style.color = fUnreadColor;
				} else {
					tUnreadCount.style.color = fUnreadColor;
				}
			} else {
				if (gMk === "HUN_A") {
					fAuthorTag.style.color = tuc;
				}
			}
		}
	}
	// Check author name
	function CHK(elem, s) {
		const result = tal.some((v) => s === v);
		if (sFa) {
			elem.style.border = result ? "thin solid fuchsia" : "thin solid dodgerblue";
		} else {
			elem.style.border = "none";
		}
		return result;
	}
	// Add fav author
	function ADA() {
		if (!sFa) {
			sFa = true;
			document.addEventListener("click", AAH, true);
		} else {
			sFa = false;
			document.removeEventListener("click", AAH, true);
			USV();
		}
		document.getElementById("cFbtn").textContent = sFa ? "💖" : "💟";
		FAV();
	}
	// Add author handler
	function AAH(e) {
		e.preventDefault();
		if (e.target.closest(fAuthor)) {
			if (gMk === "HUN_A") {
				UTL(e, e.target.href.match(/\d+$/)[0]);
			} else {
				UTL(e, e.target.textContent);
			}
			FAV();
		}
		return false;
	}
	// Update temp list
	function UTL(e, s) {
		const i = tal.findIndex((v) => v === s);
		if (i !== -1) {
			tal.splice(i,1);
		} else {
			tal.push(s);
		}
		//console.log(tal);
		return tal;
	}

	// Create float button
	function CBT() {
		const cButton = document.body.appendChild(document.createElement("button"));
		// Button style
		cButton.id = "cFbtn";
		cButton.textContent = "💟";
		cButton.style = "position: fixed; bottom: 20%; right: 10%; width: 44px; height: 44px; z-index: 9999; font-size: 200%; opacity: 50%; cursor:pointer; border: none; padding: unset;";
		cButton.type = "button";
		cButton.addEventListener("click", (e) => {
			ADA();
		});
	}

	// Auto cheering
	function CCR() {
		atc = !atc;
		const ttt = atc ? "On" : "Off";
		alert("AutoCheering is " + ttt);
		USV();
	}

	// Set unread number
	function SUN() {
		const t = parseInt(prompt("Enter unread counts", tau), 10);
		if (t >= 0) {
			tau = t;
		} else {
			tau = 3;
			alert("Invalid number, default[3] will be used.");
		}
		USV();
		FAV();
	}
	// Set unread colour
	function SUC() {
		if (!sFc) {
			sFc = true;
			document.addEventListener("click", CSH, true);
		} else {
			sFc = false;
			document.removeEventListener("click", CSH, true);
			USV();
		}
		document.getElementById("cMenu").style.display = sFc ? "" : "none";
	}

	let tCate;
	// Colour select handler
	function CSH(e) {
		e.preventDefault();
		if (e.target.classList.contains("customCates")) {
			e.target.textContent = "▣" + e.target.textContent.slice(1);
			const cca = document.getElementsByClassName("customCates");
			for(let i = 0; i < cca.length; i++) {
				cca[i].textContent = cca[i] === e.target ? "◉" + cca[i].textContent.slice(1) : "○" + cca[i].textContent.slice(1);
			}
			tCate = e.target.textContent.slice(1, 2);
			let tctc;
			switch (tCate) {
				case "0":
					tctc = tac;
					break;
				case "1":
					tctc = tcc;
					break;
				case "2":
					tctc = tuc;
					break;
			}
			const ccb = document.getElementsByClassName("customColour");
			for(let j = 0; j < ccb.length; j++) {
				ccb[j].textContent = ccb[j].style.color === tctc ? "▣ColourTest" : "▢ColourTest";
			}
		} else if (e.target.classList.contains("customColour")) {
			const cca = document.getElementsByClassName("customCates");
			for(let i = 0; i < cca.length; i++) {
				if (cca[i].textContent.slice(1, 2) === tCate) cca[i].style.color = e.target.style.color;
			}
			switch (tCate) {
				case "0":
					tac = e.target.style.color;
					break;
				case "1":
					tcc = e.target.style.color;
					break;
				case "2":
					tuc = e.target.style.color;
					break;
			}
			const ccb = document.getElementsByClassName("customColour");
			for(let j = 0; j < ccb.length; j++) {
				ccb[j].textContent = ccb[j] === e.target ? "▣ColourTest" : "▢ColourTest";
			}
			FAV();
		}
		return false;
	}
	// Create colour list
	function CMU() {
		const cMenu = document.body.appendChild(document.createElement("div"));
		cMenu.id = 'cMenu';
		const cCates = [ 'AuthorColour', 'ButtonColour', 'UnreadColour' ];
		cCates.forEach((item, index) => {
			let tctc;
			switch (index) {
				case 0:
					tctc = tac;
					break;
				case 1:
					tctc = tcc;
					break;
				case 2:
					tctc = tuc;
					break;
			}
			const cMc = cMenu.appendChild(document.createElement("p"));
			cMc.classList.add("customCates");
			cMc.style = 'position: fixed; bottom: ' + (4+index)*5 + '%; right: 22%; z-index: 9999; color: ' + tctc + '; background-color: #393939; padding: 10px;';
			cMc.type = "button";
			cMc.textContent = "○" + index + ". " + item;
		});
		const colors = ['deepskyblue', 'blue', 'lime', 'green', 'fuchsia', 'indigo', 'orange', 'red'];
		colors.forEach((item, index) => {
			const cMb = cMenu.appendChild(document.createElement("p"));
			cMb.classList.add("customColour");
			cMb.style = 'position: fixed; bottom: ' + (4+index)*5 + '%; right: 55%; z-index: 9999; color: ' + item + '; background-color: #F3F3F3; padding: 10px;';
			cMb.type = "button";
			cMb.textContent = "▢ColourTest";
		});
		cMenu.style.display = "none";
	}

	// Custom reading history
	function CRH(pele, elem, upa, upb) {
		if (!document.getElementById("rlst")) {
			const crl = document.querySelector(pele).insertBefore(document.createElement("div"), document.querySelector(elem));
			crl.id = "rlst";
			crl.style.marginBottom = "1em";
			if (gMk === "HUN_K") {
				const gBody = document.querySelector("body#page-my-antenna-worksGuest");
				if (gBody) {
					gBody.style.overflow = "auto";
				}
			}
			crl.innerHTML = '<h2 id="crh" style="cursor: pointer;">閲覧履歴▼</h2>' + 
							'<div id="rhd" style="margin: 1em 0 0 1em; display: none;"></div>';
			document.addEventListener("click", (e) => {
				if (e.target.classList.contains("drrk")) {
					const key = e.target.getAttribute("data");
					delete rrk[key];
					USV();
					CRH();
				} else if (e.target.classList.contains("gurl")) {
					const tKey = e.target.getAttribute("data_wk");
					const tEpi = e.target.getAttribute("data_ep");
					const url = "https://kakuyomu.jp/works/" + tKey;
					fetch(url).then((response) => {
						if (response.ok) {
							return response.text();
						}
						throw new Error('Something went wrong');
					})
					.then((text) => {
						const doc = new DOMParser().parseFromString(text, 'text/html');
						const data = JSON.parse(doc.getElementById("__NEXT_DATA__").innerHTML);
						const re = new RegExp("Episode:");
						const keys = data.props.pageProps.__APOLLO_STATE__;
						let i = 1;
						for (let key in keys) {
							if (re.test(key)) {
								if (i === parseInt(tEpi)) {
									const turl = url + "/episodes/" + keys[key].id;
									//console.log(i, turl);
									e.target.outerHTML = '<a href=' + turl + ' style="margin-left: 1em;">' + e.target.textContent + '</a>';
									rrk[tKey].epi = keys[key].id;
									USV();
									break;
								}
								i++;
							}
						}
					})
					.catch((error) => {
						//console.log(error);
					});
				}
			});
			document.getElementById("crh").addEventListener("click", (e) => {
				if (document.getElementById("rhd").style.display === "none") {
					document.getElementById("rhd").style.display = "";
					document.getElementById("crh").textContent = "閲覧履歴▶";
				} else {
					document.getElementById("rhd").style.display = "none";
					document.getElementById("crh").textContent = "閲覧履歴▼";
				}
			});
		}
		document.getElementById("rhd").innerHTML = "";
		Object.keys(rrk).reverse().forEach(k => {
			let vlink;
			if (gMk === "HUN_K"
			&& rrk[k].epi.length <= 4) {
				vlink = '<span class="gurl" data_wk="' + k + '" data_ep="' + rrk[k].epi + '" type="button" style="margin-left: 1em; color: indigo; cursor: pointer;">' + rrk[k].tit + '</span>';
			} else {
				vlink = '<a href=' + upa + k + upb + rrk[k].epi + ' style="margin-left: 1em;">' + rrk[k].tit + '</a>';
			}
			document.getElementById("rhd").innerHTML += '<p style="font-size: 1em; line-height: 2em;">' +
				'<span class="drrk" data="' + k + '" type="button" style="color: red; cursor: pointer;">✖</span>' + 
				'<span style="margin-left: 1em;">' + rrk[k].tim + '</span>' + 
				vlink +
			'</p>';
		});
	}
	// Create resume button
	function CRB(key, elem, upa, upb) {
		const tbtn = document.querySelector(elem).appendChild(document.createElement("a"));
		tbtn.href = upa + key + upb + rrk[key].epi;
		tbtn.textContent = "▶続きから読む";
		tbtn.style = "margin-left: 1em; color: dodgerblue; cursor: pointer;";
	}
	// Create import button
	function CIB(pele, node, ktit, epi) {
		const iBtn = document.querySelector(pele).appendChild(document.createElement("span"));
		iBtn.textContent = "⇲履歴登録";
		iBtn.style = "margin-left: 1em; color: dodgerblue; cursor: pointer;";
		iBtn.addEventListener("click", (e) => {
			const no = document.getElementsByClassName(node);
			for (let i = 0; i < no.length; i++) {
				switch (location.host) {
					case "kakuyomu.jp": {
						const cno = no[i].querySelectorAll("li");
						let cepi;
						if (uRi.includes("histo")) {
							cepi = (cno.length === 2)
									? cno[1].textContent.slice(3,-1)
									: (cno[2].textContent.slice(3,-1) - cno[1].textContent.slice(2,-1)).toString();
						} else {
							cepi = (cno.length === 2)
									? cno[0].textContent.slice(3,-1)
									: (cno[1].textContent.slice(3,-1) - cno[0].textContent.slice(2,-1)).toString();
						}
						ANH(no[i].querySelector(epi).href.split("/")[4], cepi, no[i].querySelector(ktit).textContent);
						break;
					}
					case "syosetu.com":
					case "yomou.syosetu.com": {
						const title = location.host.startsWith("y")
								? no[i].querySelector(ktit).textContent.slice(0, 12)
								: no[i].querySelector(ktit).textContent.slice(3, 15);
						ANH(no[i].querySelector(ktit).href.split("/")[3], no[i].querySelector(epi).href.split("/")[4], title);
						break;
					}
				}
			}
			alert("result: " + JSON.stringify(rrk));
		}, { once: true });
	}
	// Add new history
	function ANH(key, epi, title) {
		const tim = new Date().toISOString().split('T')[0];
		if(Object.hasOwn(rrk, key)) {
			delete rrk[key];
		}
		rrk[key] = {"epi": null, "tit": null, "tim": null};
		if (title.length > 12) {
			title = title.slice(0, 12);
		}
		rrk[key].tit = title;
		rrk[key].epi = epi;
		rrk[key].tim = tim;
		USV();
	}

	// Todo: merge all episodes in one txt
	// Download all switch
	function DAS() {
		sdb = !sdb;
		const eee = sdb ? "On" : "Off";
		alert("Show download all button is " + eee);
		USV();
	}
	//Download all episodes button
	function DAB(title, url, elem, pele) {
		if (sdb && !document.querySelector("a.dAbtn")) {
			sleep(2000).then(() => {
				const dAbtn = document.createElement("a");
				dAbtn.classList.add("dAbtn");
				dAbtn.style = "border: medium none; color: red; margin: 2em; cursor: pointer;";
				dAbtn.textContent = "📥全て保存";
				dAbtn.addEventListener("click", (e) => {
					switch (gMk) {
						case "HUN_N":
							NGL(title, url, elem);
							break;
						case "HUN_K":
							KGL(title, url, elem);
					}
				});
				document.querySelector(pele).appendChild(dAbtn);
			});
		}
	}
	// Narou get episodes from download page
	function NGL(title, url, elem) {
		fetch(url).then((response) => {
			if (response.ok) {
				return response.text();
			}
			throw new Error('Something went wrong');
		})
		.then(async (text) => {
			const doc = new DOMParser().parseFromString(text, 'text/html');
			const data = doc.querySelector(elem).textContent;
			const min = parseInt(prompt("Enter a start episode number", "1"));
			const max = parseInt(prompt("Enter a end episode number", "2"));
			let nlc = "";
			if (min >= 1 && min <= max) {
				const ept = data.split("\n");
				for (let i = 1; i < data.split("\n").length - 1; ) {
					const tit = i + ". " + ept[i];
					const url = uRi + i + "/";
					// download episode base on input range
					if (i >= min && i <= max) {
						await sleep(Math.floor((Math.random() * (10000 - 5000 + 1)) + 5000)).then(() => {
							nlc += tit + ": " + url + "\n";
							//console.log(tit, url);
							GEC(tit, url, "div#novel_honbun");
						});
					}
					i++;
				}
			} else {
				alert("Invalid Inputs");
			}
			SAT(nlc);
		})
		.catch((error) => {
			//console.log(error);
		});
	}
	// Kakuyomu get episodes list from novel page
	async function KGL(title, url, elem) {
		const data = JSON.parse(document.getElementById(elem).innerHTML);
		const re = new RegExp("Episode:");
		const keys = data.props.pageProps.__APOLLO_STATE__;
		const min = parseInt(prompt("Enter a start episode number", "1"));
		const max = parseInt(prompt("Enter a end episode number", "2"));
		let nlc = "";
		if (min >= 1 && min <= max) {
			let i = 1;
			for (let key in keys) {
				if (re.test(key)) {
					const ttit = i + ". " + keys[key].title;
					const turl = url + "/episodes/" + keys[key].id;
					// download episode base on input range
					if (i >= min && i <= max) {
						await sleep(Math.floor((Math.random() * (10000 - 5000 + 1)) + 5000)).then(() => {
							nlc += ttit + ": " +turl + "\n";
							//console.log(nlc);
							GEC(ttit, turl, "div.widget-episodeBody");
						});
					}
					i++;
				}
			}
		} else {
			alert("Invalid Inputs");
		}
		SAT(nlc);
	}
	// Get episode content
	function GEC(title, url, elem) {
		fetch(url).then((response) => {
			if (response.ok) {
				return response.text();
			}
			throw new Error('Something went wrong');
		})
		.then((text) => {
			const doc = new DOMParser().parseFromString(text, 'text/html');
			const data = doc.querySelector(elem).textContent;
			SAT(data);
		})
		.catch((error) => {
			//console.log(error);
		});
	}
	// Save as txt
	function SAT(text) {
		//console.log(text);
		const a = document.createElement("a");
		a.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent(text);
		a.download = title + '.txt';
		document.body.appendChild(a);
		a.click();
		document.body.removeChild(a);
	}
	// Download current epicode as txt
	function TXT(elem, pele, title) {
		const data = document.querySelector(elem).textContent;
		const dButton = document.querySelector(pele).appendChild(document.createElement("a"));
		dButton.textContent = "📥ダウンロード";
		dButton.setAttribute('download', title + '.txt');
		dButton.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(data));
	}
})();