Greasy Fork is available in English.

Novel Sites Enhance

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

ติดตั้งสคริปต์นี้?
สคริปต์ที่แนะนำของผู้เขียน

คุณอาจชื่นชอบ Novel Ranking Comments Filter

ติดตั้งสคริปต์นี้
// ==UserScript==
// @name           Novel Sites Enhance
// @name:ja        小説サイト機能強化
// @namespace      https://greasyfork.org/en/users/1264733
// @version        2024-07-16
// @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_addStyle
// @grant          GM_getValue
// @grant          GM_setValue
// @grant          GM_listValues
// @grant          GM_registerMenuCommand
// ==/UserScript==

(()=>{
	'use strict';

	let gMk, tlo, fAuthor, sFa, sFb, sFh, sFo;
	sFa = false;
	sFb = false;
	sFh = false;
	sFo = false;
	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("Option", OPT);
	// Read List
	URD();
	function URD() {
		const trc = GM_getValue(gMk);
		tlo = trc ? trc : { ATC:false, AUH:false, SDB:false, DTF:false, FAC:"indigo", FCC:"orange", FUC:"red", FAU:3, FBL:[2,4,2], FDT:[10000,5000,0], FAL:[], RRK:{} };
	}
	// Save List
	function USV() {
		if (gMk && JSON.stringify(tlo) !== JSON.stringify(GM_getValue(gMk))) {
			GM_setValue(gMk, tlo);
		}
	}
	const uRi = location.href;
	const rMb = navigator.userAgent.includes("Mobile");
	const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
	// Favorite page css
	let fss = (`:root {background-color: #fefefe; filter: invert(1) hue-rotate(180deg);}
				* {background-color: inherit;}`);
	switch (true) {
		case uRi.includes("/my/"):
		case uRi.includes("/favnovelmain/"):
		case uRi.includes("/mypage/"):
			GM_registerMenuCommand("Select", ADA);
			if (tlo.DTF) {
				GM_addStyle(fss);
			}
			FAV();
			break;
		case /^https:\/\/ncode\.syosetu\.com\/[A-z0-9]+\/?$/.test(uRi): {
			if(document.getElementById("novel_honbun")) {
				EPI();
			} else {
				// Add resume button(base on casutom history)
				if (tlo.AUH) {
					const cKey = location.pathname.split("/")[1];
					if(Object.hasOwn(tlo.RRK, cKey)) {
						CRB(cKey, "p.novel_title", "https://ncode.syosetu.com/", "/");
					}
				}
			}
			break;
		}
		case uRi.includes("/rireki"):
			//console.log("DoNothing");
			break;
		case /^https:\/\/kakuyomu\.jp\/works\/[0-9]+$/.test(uRi): {
			// Add resume button(base on custom history)
			if (tlo.AUH) {
				URD();
				sleep(2000).then(() => {
					const cKey = location.pathname.split("/")[2];
					if (Object.hasOwn(tlo.RRK, cKey) && tlo.RRK[cKey].epi.length <= 4) {
						const data = JSON.parse(document.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(tlo.RRK[cKey].epi)) {
									tlo.RRK[cKey].epi = keys[key].id;
									USV();
									break;
								}
								i++;
							}
						}
					}
					CRB(cKey, "a[title]", "https://kakuyomu.jp/works/", "/episodes/");
				});
			}
			break;
		}
		default:
			EPI();
	}

	// Episode page
	function EPI() {
		if (tlo.AUH) {
			URD();
		}
		// 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")
					&& tlo.FAL.some(name => document.title.includes('(' + name + ') -')) ) {
					rMf = true;
				}
				if (tlo.AUH) {
					ANH(location.pathname.split("/")[2], location.pathname.split("/")[4], document.querySelector("h1.js-vertical-composition-item>a").title, null);
				}
				break;
			}
			case "HUN_N": {
				eCb = document.querySelector("a.js-novelgood_change");
				if ( document.querySelector("div.is-empty")
					&& tlo.FAL.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();
					});
				}
				if (tlo.AUH) {
					ANH(location.pathname.split("/")[1], location.pathname.split("/")[2], document.title.split(" - ")[0], null);
				}
				break;
			}
			case "HUN_A":
				eCb = document.getElementById("contentMangaLikeBtnCircle");
				if ( !eCb.classList.contains("max")
					&& tlo.FAL.some(name => uRi.includes(name)) ) {
					rMf = true;
				}
				break;
		}
		// Save updated custom reading history
		if (tlo.AUH) {
			USV();
		}
		const ioc = new IntersectionObserver((entries) => {
			if (entries[0].intersectionRatio <= 0) return;
			ioc.disconnect();
			if (rMf) {
				eCb.style.backgroundColor = tlo.FCC;
				if (tlo.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 = "";
						}
					});
				}
			}
		});
		if (gMk !== "HUN_N" || !document.querySelector("span.p-novelgood-form__status")) {
			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)
										? tlo.FAC
										: "";
			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) ? tlo.FAC
									: tUnreadCount && tUnreadNum > tlo.FAU ? tlo.FUC
									: "";
			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 = tlo.FUC;
				}
			}
		}
	}
	// Check author name
	function CHK(elem, s) {
		const result = tlo.FAL.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) {
			URD();
			sFa = true;
			document.addEventListener("click", AAH);
		} else {
			sFa = false;
			document.removeEventListener("click", AAH);
			USV();
		}
		document.getElementById("nse_asb").textContent = sFa ? "💖" : "💟";
		FAV();
	}
	// Add author handler
	function AAH(e) {
		e.preventDefault();
		if (e.target.closest(fAuthor)) {
			if (gMk === "HUN_A") {
				UTL(e.target.href.match(/\d+$/)[0]);
			} else {
				UTL(e.target.textContent);
			}
			FAV();
		}
	}
	// Update temp list
	function UTL(s) {
		const i = tlo.FAL.findIndex((v) => v === s);
		if (i !== -1) {
			tlo.FAL.splice(i,1);
		} else {
			tlo.FAL.push(s);
		}
		//console.log(tlo.FAL);
		return tlo.FAL;
	}

	// Create float buttons
	CFB();
	function CFB() {
		if (!document.getElementById("nse_fcm")) {
			const cDiv = document.body.appendChild(document.createElement("div"));
			cDiv.id = "nse_fcm";
		}
		const posx = tlo.FBL[0], posy = tlo.FBL[1], diff = tlo.FBL[2];
		BCR("nse_fcb", "💠", posx, posy, "yellow", "");
		BCR("nse_opt", "Ⓜ", posx + diff, posy, "blue", "none");
		if (tlo.AUH) {
			BCR("nse_rhb", "🕓", posx, posy + diff, "#4baae0", "none");
		}
		switch (true) {
			case uRi.includes("/antenna/works"):
			case uRi.includes("/favnovelmain/"):
				BCR("nse_asb", "💟", posx + diff, posy + diff, "red", "none");
				break;
			case /^https:\/\/ncode\.syosetu\.com\/[A-z0-9]+/.test(uRi):
			case /^https:\/\/kakuyomu\.jp\/works\/[0-9]+/.test(uRi):
				if (tlo.SDB) {
					BCR("nse_dlb", "📥", posx + diff, posy + diff, "red", "none");
				}
			break;
		}
		document.getElementById("nse_fcb").addEventListener("click", SCB);
	}
	// Button creater
	function BCR(id, text, posx, posy, color, show) {
		const cBtn = document.getElementById("nse_fcm").appendChild(document.createElement("button"));
		cBtn.id = id;
		if (id === "nse_fcb") {
			//console.log("💠");
		} else {
			cBtn.classList.add("nse_cfb");
		}
		cBtn.textContent = text;
		cBtn.style = "position: fixed; width: 44px; height: 44px; z-index: 9999; font-size: 200%; opacity: 50%; cursor:pointer; border: none; padding: unset; right: " + posx + "em; bottom: " + posy + "em; color: " + color + "; display: " + show + ";";
		cBtn.type = "button";
	}
	// Menu creater
	function MCR(id) {
		const cMenu = document.body.appendChild(document.createElement("div"));
		cMenu.id = id;
		const cPos = !rMb ? " width: 50%; left: 25%; height: 80%;" : " width: 98%; left: 1%; height: 52%;";
		cMenu.style = "position: fixed;" + cPos + " overflow-y: scroll; overflow-wrap: break-word; top:10%; z-index: 9999; background-color: #f1f3f5; display: none;";
	}
	// Show child button
	function SCB() {
		if (!sFb) {
			sFb = true;
			document.getElementById("nse_fcm").addEventListener("click", FBH);
		} else {
			sFb = false;
			document.getElementById("nse_fcm").removeEventListener("click", FBH);
		}
		const no = document.getElementsByClassName("nse_cfb");
		for (let i = 0; i < no.length; i++) {
			no[i].style.display = sFb ? "" : "none";
		}
	}
	// Buttons handler
	function FBH(e) {
		switch (e.target.id) {
			// Options button
			case "nse_opt":
				OPT();
				break;
			// Author select button
			case "nse_asb":
				ADA();
				break;
			// Reading history button
			case "nse_rhb": {
				if (!document.getElementById("nse_rhp")) {
					MCR("nse_rhp");
				}
				const crhp = document.getElementById("nse_rhp");
				if (!sFh) {
					URD();
					sFh = true;
					crhp.style.display = "";
					if (gMk === "HUN_K") {
						CRH("nse_rhp", "https://kakuyomu.jp/works/", "/episodes/");
					} else if (gMk === "HUN_N") {
						CRH("nse_rhp", "https://ncode.syosetu.com/", "/");
					}
					crhp.addEventListener("click", RHB);
				} else {
					sFh = false;
					crhp.style.display = "none";
					// Clear history
					crhp.innerHTML = "";
					crhp.removeEventListener("click", RHB);
					USV();
				}
				break;
			}
			// Download button
			case "nse_dlb":
				if ( /^https:\/\/ncode\.syosetu\.com\/[A-z0-9]+\/?$/.test(uRi)
				|| /^https:\/\/kakuyomu\.jp\/works\/[0-9]+$/.test(uRi) ) {
					// Add download all episodes button
					if (gMk === "HUN_N") {
						NGL(document.title, document.querySelector("ul.undernavi li:nth-child(2) a").href, 'select[name="no"]');
					} else {
						KGL(document.title.split("(")[0], uRi, "__NEXT_DATA__");
					}
				} else {
					// Add download current episodes button
					DCE();
				}
				break;
			default:
				//console.log("DoNothing");
		}
	}
	// Reading history button
	function RHB(e) {
		if (e.target.id === "nse_shb") {
			// Save history button
			let htit, upa, upb;
			if (gMk === "HUN_K") {
				htit = "カクヨム閲覧履歴";
				upa = "https://kakuyomu.jp/works/";
				upb = "/episodes/";
			} else if (gMk === "HUN_N") {
				htit = "なろう閲覧履歴";
				upa = "https://ncode.syosetu.com/";
				upb = "/";
			}
			let htxt = "";
			Object.keys(tlo.RRK).reverse().forEach(k => {
				const vbmk = tlo.RRK[k].bmk === "1" ? " 💖 "
							: tlo.RRK[k].bmk === "2" ? " 🖤 "
							: " ❓ ";
				let kpt = "";
				let vlink = upa + k + upb + tlo.RRK[k].epi;
				if (gMk === "HUN_K") {
					if (tlo.RRK[k].epi.length <= 4) {
						kpt = "[" + tlo.RRK[k].epi + "]";
						vlink = upa + k + "/resume_reading";
					}
				}
				htxt += tlo.RRK[k].tim + vbmk + tlo.RRK[k].tit.replace(/\n/g,'') + kpt + ": " + vlink + "\n";
			});
			SAT(htit, htxt);
			alert("File downloads completed");
		} else if (e.target.id === "nse_ihb") {
			// Import history button
			if (gMk === "HUN_K") {
				CIB("widget-antennaList-item", "h4.widget-antennaList-title", "a.widget-antennaList-continueReading");
			} else if (gMk === "HUN_N") {
				if (location.host.startsWith("y")) {
					CIB("p-rireki-item", "a.c-card__title", "div.p-rireki-item__button-link>a.c-button");
				} else {
					CIB("p-up-bookmark-item", "div.p-up-bookmark-item__title>a", "a.c-button--sm");
				}
			}
		} else if (e.target.className === "nse_drh") {
			// Delete history button
			const key = e.target.getAttribute("data");
			delete tlo.RRK[key];
			CRH();
		} else if (e.target.className === "nse_brh") {
			// Bookmark history button
			const key = e.target.getAttribute("data");
			tlo.RRK[key].bmk = tlo.RRK[key].bmk === "1" ? "2" : "1";
			CRH();
		}
	}
	// Options
	function OPT() {
		if (!document.getElementById("nse_omu")) {
			MCR("nse_omu");
		}
		const comu = document.getElementById("nse_omu");
		if (!sFo) {
			URD();
			sFo = true;
			UOM();
			comu.style.display = "";
			comu.addEventListener("click", OMB);
		} else {
			sFo = false;
			comu.style.display = "none";
			comu.innerHTML = "";
			comu.removeEventListener("click", OMB);
			if (JSON.stringify(tlo) !== JSON.stringify(GM_getValue(gMk))) {
				GM_setValue(gMk, tlo);
				const t = prompt("New options will be applied after reload current page, reload now?", "Yes");
				if (t === "Yes") {
					location.reload();
				}
			}
		}
	}
	// Update Options Menu
	function UOM() {
		const comu = document.getElementById("nse_omu");
		const ost = "margin-left: 1em; cursor: pointer;";
		comu.innerHTML = `<h2>Options:</h2>
			<p id="nse_inp" style="${ost} background-color: cyan;" contenteditable>Paste options here</p>
			<p id="nse_iop" style="${ost}">📤 Import options from above</p>
			<p id="nse_sop" style="${ost}">💾 Save options to local txt</p>`;
		const mItems = [ "AutoCheering [true:On false:Off]", "LocalHistory [true:On false:Off]", "DownloadButton [true:On false:Off]", "DarkFavoritepage [true:On false:Off]","AuthorColour", "CheeringButtonColour", "UnreadColour", "UnreadCounts [default:3]", "ButtonPositon [default(right,bottom,dist):2,4,2]", "DownloadSettings [default(maxdelay(ms),mindelay(ms),type:0.Separate else.combine):10000,5000,0]" ];
		mItems.forEach((item, index) => {
			let bna, val;
			const psb = '<p style="font-size: 1em; line-height: 2em; margin: 1em;">' + item + ': <span type="button" style="border: thin solid dodgerblue;';
			const pse = '</span></p>';
			switch (index) {
				case 0:
					bna = "ATC";
					break;
				case 1:
					bna = "AUH";
					break;
				case 2:
					bna = "SDB";
					break;
				case 3:
					bna = "DTF";
					break;
				case 4:
					bna = "FAC";
					break;
				case 5:
					bna = "FCC";
					break;
				case 6:
					bna = "FUC";
					break;
				case 7:
					bna = "FAU";
					break;
				case 8:
					bna = "FBL";
					break;
				case 9:
					bna = "FDT";
					break;
			}
			if (index <= 3) {
				comu.innerHTML += psb + '" class="nse_swh" data="' + bna + '">' + tlo[bna] + pse;
			} else if (index > 3 && index < 7) {
				comu.innerHTML += psb + 'color:' + tlo[bna] + '; display: inline-block; min-width: 2em;" contenteditable>' + tlo[bna] + '</span><span class="nse_inc" data="' + bna + '"> ✅</span><span class="nse_csb" data="' + bna + '"> 🎨' + pse;
			} else {
				comu.innerHTML += psb + ' display: inline-block; min-width: 2em;" contenteditable>' + tlo[bna] + '</span><span class="nse_inn" data="' + bna + '"> ✅' + pse;
			}
		});
	}
	// Option menu button
	function OMB(e) {
		const eT = e.target;
		if (eT.id === "nse_iop") {
			let copt = document.getElementById("nse_inp").innerText;
			if ( copt.startsWith("{") ) {
				copt = copt.slice(1, -1).split("\n");
				for (let i=1; i<copt.length-1; i++) {
					let ttt = i === (copt.length-2) ? copt[i] : copt[i].slice(0, -1);
					if (ttt.slice(1, 6) === gMk) {
						tlo = JSON.parse(ttt.slice(9));
						UOM();
					} else {
						GM_setValue(ttt.slice(1, 6), JSON.parse(ttt.slice(9)));
					}
				}
				document.getElementById("nse_inp").innerText = "Import completed";
			} else {
				document.getElementById("nse_inp").innerText = "Invalid input";
			}
		} else if (eT.id === "nse_sop") {
			const ctim = new Date().toISOString().split('T')[0];
			let copt = "{\n";
			const keys = GM_listValues();
			keys.forEach(k => {
				copt += '"' + k + '": ' + JSON.stringify(GM_getValue(k)) + ',\n';
			});
			SAT("nse_bak_" + ctim, copt.slice(0, -2) + "\n}");
			alert("File downloads completed");
		} else if (eT.className === "nse_swh") {
			const key = eT.getAttribute("data");
			tlo[key] = !tlo[key];
			eT.textContent = tlo[key];
		} else if (eT.className === "nse_inc") {
			const ePf = eT.parentElement.querySelector("span");
			const key = eT.getAttribute("data");
			tlo[key] = ePf.textContent;
			ePf.style.color = ePf.textContent;
		} else if (eT.className === "nse_csb") {
			const ePf = eT.parentElement.querySelector("span");
			CCL(eT, ePf);
		} else if (eT.className === "nse_inn") {
			const ePf = eT.parentElement.querySelector("span");
			let num;
			if (ePf.textContent.includes(",")) {
				num = JSON.parse("[" + ePf.textContent + "]");
			} else {
				num = parseInt(ePf.textContent);
			}
			const key = eT.getAttribute("data");
			tlo[key] = num;
		}
	}
	// Colour list
	function CCL(elem, pele) {
		const clst = elem.appendChild(document.createElement("dialog"));
		clst.id = "nse_clst";
		let cContent = `<form method="dialog">
			<p>
				<label>Select a colour:
					<select>`;
		const colors = ['deepskyblue', 'blue', 'lime', 'green', 'fuchsia', 'indigo', 'orange', 'red'];
		colors.forEach((item) => {
			cContent += `<option style="color: ${item};">${item}</option>`;
		});
		cContent += `</select>
				</label>
			</p>
			<menu>
				<button value="cancel" formmethod="dialog">Cancel</button>
				<button id="confirmBtn" value="default">Confirm</button>
			</menu>
		</form>`;
		clst.innerHTML = cContent;
		clst.showModal();
		const selectEl = clst.querySelector("select");
		const confirmBtn = clst.querySelector("#confirmBtn");
		clst.addEventListener("close", (e) => {
			if (clst.returnValue !== "cancel" && clst.returnValue !== "") {
				pele.textContent = clst.returnValue;
				const key = elem.getAttribute("data");
				tlo[key] = pele.textContent;
				pele.style.color = pele.textContent;
			}
			clst.innerHTML = "";
			elem.removeChild(clst);
		});
		confirmBtn.addEventListener("click", (event) => {
			event.preventDefault();
			clst.close(selectEl.value);
		});
	}
	// Custom reading history
	function CRH(elem, upa, upb) {
		if (!document.getElementById("nse_rhd")) {
			const crl = document.getElementById(elem);
			if (gMk === "HUN_K") {
				const gBody = document.querySelector("body#page-my-antenna-worksGuest");
				if (gBody) {
					gBody.style.overflow = "auto";
				}
			}
			let cihb = "";
			if ( uRi.includes("/antenna/works")
			|| uRi.includes("/favnovelmain/")
			|| uRi.includes("_histor") 
			|| uRi.includes("/rireki") ) {
				cihb = '<p id="nse_ihb" style="margin-left: 1em; cursor: pointer;">💕 Import from current page</p>';
			}
			crl.innerHTML = '<h2>閲覧履歴:</h2>' +
								cihb + 
								'<p id="nse_shb" style="margin-left: 1em; cursor: pointer;">💾 Save history to local txt</p>' + 
							'<div id="nse_rhd" style="margin: 1em 0 0 1em;"></div>';
		}
		document.getElementById("nse_rhd").innerHTML = "";
		Object.keys(tlo.RRK).reverse().forEach(k => {
			// Update version patch
			if (!tlo.RRK[k].bmk) {
				tlo.RRK[k].bmk = null;
			}
			const vbmk = tlo.RRK[k].bmk === "1" ? " 💖 "
						: tlo.RRK[k].bmk === "2" ? " 🖤 "
						: " ❓ ";
			const vlink = (gMk === "HUN_K" && (tlo.RRK[k].epi.length <= 4))
						? '<a href="' + upa + k + '/resume_reading" data="' + tlo.RRK[k].epi + '" style="margin-left: 1em;">' + tlo.RRK[k].tit + '</a>'
						: '<a href="' + upa + k + upb + tlo.RRK[k].epi + '" data="' + tlo.RRK[k].epi + '" style="margin-left: 1em;">' + tlo.RRK[k].tit + '</a>';
			document.getElementById("nse_rhd").innerHTML += '<p style="font-size: 1em; line-height: 2em;">' +
				'<span class="nse_drh" data="' + k + '" type="button" style="color: red; cursor: pointer;">✖</span>' + 
				'<span style="margin-left: 1em;">' + tlo.RRK[k].tim + '</span>' + 
				'<span class="nse_brh" data="' + k + '" style="margin-left: 1em; cursor: pointer;">' + vbmk + '</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 + tlo.RRK[key].epi;
		tbtn.textContent = "▶続きから読む";
		tbtn.style = "margin-left: 1em; color: dodgerblue; cursor: pointer;";
	}
	// Create import button
	function CIB(node, etit, eepi) {
		URD();
		let bookmark;
		const no = document.getElementsByClassName(node);
		for (let i = no.length - 1; i >= 0; 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();
						bookmark = null;
					} else {
						cepi = (cno.length === 2)
								? cno[0].textContent.slice(3,-1)
								: (cno[1].textContent.slice(3,-1) - cno[0].textContent.slice(2,-1)).toString();
						bookmark = "1";
					}
					ANH(no[i].querySelector(eepi).href.split("/")[4], cepi, no[i].querySelector(etit).textContent, bookmark);
					break;
				}
				case "syosetu.com":
				case "yomou.syosetu.com": {
					let title;
					if (location.host.startsWith("y")) {
						title = no[i].querySelector(etit).textContent.slice(0, 12);
						bookmark = null;
					} else {
						title = no[i].querySelector(etit).textContent.slice(3, 15);
						bookmark = "1";
					}
					ANH(no[i].querySelector(etit).href.split("/")[3], no[i].querySelector(eepi).href.split("/")[4], title, bookmark);
					break;
				}
			}
		}
		USV();
		alert("result: " + JSON.stringify(tlo.RRK));
	}
	// Add new history
	function ANH(key, epi, title, bmk) {
		let tim = new Date().toISOString().split('T')[0];
		if ( !uRi.includes("/favnovelmain/") 
		&& !uRi.includes("/antenna/works") ) {
			if (Object.hasOwn(tlo.RRK, key)) {
				if ( uRi.includes("_histor")
				|| uRi.includes("/rireki") ) {
					tim = tlo.RRK[key].tim;
				}
				bmk = tlo.RRK[key].bmk;
				delete tlo.RRK[key];
			}
		} else {
			if (Object.hasOwn(tlo.RRK, key)) {
				tlo.RRK[key].bmk = bmk;
			}
		}
		if (!Object.hasOwn(tlo.RRK, key)) {
			if (title.length > 12) {
				title = title.slice(0, 12);
			}
			tlo.RRK[key] = {"epi": epi, "tit": title, "tim": tim, "bmk": bmk};
		}
	}

	// 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() * (tlo.FDT[0] - tlo.FDT[1] + 1)) + tlo.FDT[1])).then(() => {
							nlc += tit + ": " + url + "\n";
							//console.log(tit, url);
							GEC(tit, url, "div#novel_honbun");
						});
					}
					i++;
				}
			} else {
				alert("Invalid Inputs");
			}
			WLD(title, nlc, "nse_sml");
		})
		.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() * (tlo.FDT[0] - tlo.FDT[1] + 1)) + tlo.FDT[1])).then(() => {
							nlc += ttit + ": " +turl + "\n";
							//console.log(nlc);
							GEC(ttit, turl, "div.widget-episodeBody");
						});
					}
					i++;
				}
			}
		} else {
			alert("Invalid Inputs");
		}
		WLD(title, nlc, "nse_sml");
	}
	// 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;
			if (tlo.FDT[2] === 0) {
				// Separate txt
				SAT(title, data);
			} else {
				// Combine
				SML(title, data);
			}
		})
		.catch((error) => {
			//console.log(error);
		});
	}
	// Download current epicode as txt
	function DCE() {
		let elem, title;
		if (gMk === "HUN_K") {
			elem = "div.widget-episodeBody";
			title = document.querySelector("p.widget-episodeTitle")
					? document.querySelector("p.widget-episodeTitle").textContent
					: document.title.replace(/\s/g,"").match(/[^-]+/);
		} else if (gMk === "HUN_N") {
			elem = "div#novel_honbun";
			title = document.querySelector("p.novel_subtitle")
					? document.querySelector("p.novel_subtitle").textContent
					: document.title.split("-")[1];
		}
		const data = document.querySelector(elem).textContent;
		SAT(title, data);
		alert("File downloads completed");
	}
	// Save as txt
	function SAT(title, text) {
		//console.log(text);
		const link = document.createElement("a");
		link.href = "data:text/plain;charset=utf-8," + encodeURIComponent(text);
		link.download = title + ".txt";
		link.style.display = "none";
		document.body.appendChild(link);
		link.click();
		document.body.removeChild(link);
	}
	// Save multiple data to link
	function SML(title, text) {
		if (!document.getElementById("nse_sml")) {
			const link = document.body.appendChild(document.createElement("a"));
			link.id = "nse_sml";
			link.href = "data:text/plain;charset=utf-8,";
			link.style.display = "none";
		}
		document.getElementById("nse_sml").href += encodeURIComponent(title + "\n" + text + "\n");
	}
	// Wait link data are all ready
	function WLD(title, eplst, elem) {
		sleep(tlo.FDT[0]).then(() => {
			// Save episodes list
			SAT(title + "_目次", eplst);
			// Save episodes content
			const clnk = document.getElementById(elem);
			if(clnk) {
				clnk.download = title + ".txt";
				clnk.click();
				document.body.removeChild(clnk);
				alert("File downloads completed");
			}
		});
	}
})();