Novel Sites Enhance

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

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

// ==UserScript==
// @name           Novel Sites Enhance
// @name:ja        小説サイト機能強化
// @namespace      https://gf.qytechs.cn/en/users/1264733
// @version        2024-06-08 v2
// @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("AutoCheering", CCR);
	GM_registerMenuCommand("LocalHistory", URH);
	GM_registerMenuCommand("DownloadText", DAS);
	GM_registerMenuCommand("AuthorSelect", ADA);
	GM_registerMenuCommand("UnreadCounts", SUN);
	GM_registerMenuCommand("CustomColour", SUC);
	GM_registerMenuCommand("ButnPosition", BPS);
	// Read list
	const URD = GM_getValue(gMk);
	let tlo = URD ? URD : { ATC:false, AUH:false, SDB:false, FAC:"indigo", FCC:"orange", FUC:"red", FAU:3, FAL:[], RRK:{} };
	let atc = tlo.ATC ? tlo.ATC : false;
	let auh = tlo.AUH ? tlo.AUH : 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;
	// Right, bottom, move dist, icon dist
	let tbl = tlo.FBL ? tlo.FBL : [4, 4, 1, 2];
	let rrk = tlo.RRK ? tlo.RRK : {};
	const tal = tlo.FAL;
	// Save list
	function USV() {
		tlo = { ATC:atc, AUH:auh, SDB:sdb, FAC:tac, FCC:tcc, FUC:tuc, FAU:tau, FBL:tbl, FAL:tal, RRK:rrk };
		GM_setValue(gMk, tlo);
	}
	let sFa, sFb, sFc, sFd, sFe;
	sFa = sFb = sFc = sFd = sFe = false;
	let fAuthor;
	const uRi = location.href;
	const rMb = navigator.userAgent.includes("Mobile");
	const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

	switch (true) {
		case uRi.includes("/my/"):
		case uRi.includes("/favnovelmain/"):
		case uRi.includes("/mypage/"):
			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 (auh) {
					const cKey = location.pathname.split("/")[1];
					if(Object.hasOwn(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 (auh) {
				sleep(2000).then(() => {
					const cKey = location.pathname.split("/")[2];
					if (Object.hasOwn(rrk, cKey) && 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(rrk[cKey].epi)) {
									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() {
		// 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;
				}
				if (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")
					&& 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();
					});
				}
				if (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")
					&& tal.some(name => uRi.includes(name)) ) {
					rMf = true;
				}
				break;
		}
		// Save updated custom reading history
		if (auh) {
			USV();
		}
		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 = "";
						}
					});
				}
			}
		});
		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)
										? 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("nse_asb").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;
	}
	// Auto cheering
	function CCR() {
		atc = !atc;
		const ttt = atc ? "On" : "Off";
		alert("AutoCheering is " + ttt);
		USV();
	}
	// Auto update reading history
	function URH() {
		auh = !auh;
		const ttt = auh ? "On" : "Off";
		alert("AutoUpdateHistory 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 ( uRi.includes("/my/") 
		|| uRi.includes("/favnovelmain/") 
		|| uRi.includes("/mypage/") ) {
			let ccmu;
			if (!sFc) {
				sFc = true;
				CMU();
				ccmu = document.getElementById("nse_cmu");
				ccmu.style.display = "";
				ccmu.addEventListener("click", CSH, true);
			} else {
				sFc = false;
				ccmu = document.getElementById("nse_cmu");
				ccmu.style.display = "none";
				ccmu.removeEventListener("click", CSH, true);
				USV();
			}
		}
	}
	let tCate;
	// Colour select handler
	function CSH(e) {
		e.preventDefault();
		if (e.target.classList.contains("nse_cca")) {
			e.target.textContent = "▣" + e.target.textContent.slice(1);
			const cca = document.getElementsByClassName("nse_cca");
			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("nse_cco");
			for(let j = 0; j < ccb.length; j++) {
				ccb[j].textContent = ccb[j].style.color === tctc ? "▣ColourTest" : "▢ColourTest";
			}
		} else if (e.target.classList.contains("nse_cco")) {
			const cca = document.getElementsByClassName("nse_cca");
			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("nse_cco");
			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() {
		if (!document.getElementById("nse_cmu")) {
			const cMenu = document.body.appendChild(document.createElement("div"));
			cMenu.id = "nse_cmu";
			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("nse_cca");
				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("nse_cco");
				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(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";
				}
			}
			crl.innerHTML = '<h2>閲覧履歴:</h2>' + 
							'<div id="nse_rhd" style="margin: 1em 0 0 1em;"></div>';
		}
		document.getElementById("nse_rhd").innerHTML = "";
		Object.keys(rrk).reverse().forEach(k => {
			// Update version patch
			if (!rrk[k].bmk) {
				rrk[k].bmk = null;
			}
			const vbmk = rrk[k].bmk === "1" ? " 💖 "
						: rrk[k].bmk === "2" ? " 🖤 "
						: " ❓ ";
			const vlink = (gMk === "HUN_K" && (rrk[k].epi.length <= 4))
						? '<a href="' + upa + k + '/resume_reading" data="' + rrk[k].epi + '" style="margin-left: 1em;">' + rrk[k].tit + '</a>'
						: '<a href="' + upa + k + upb + rrk[k].epi + '" data="' + rrk[k].epi + '" style="margin-left: 1em;">' + 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;">' + 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 + rrk[key].epi;
		tbtn.textContent = "▶続きから読む";
		tbtn.style = "margin-left: 1em; color: dodgerblue; cursor: pointer;";
	}
	// Create import button
	function CIB(node, etit, eepi) {
		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(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(rrk, key)) {
				if ( uRi.includes("_histor")
				|| uRi.includes("/rireki") ) {
					tim = rrk[key].tim;
				}
				bmk = rrk[key].bmk;
				delete rrk[key];
			}
		} else {
			rrk[key].bmk = bmk;
		}
		if (!Object.hasOwn(rrk, key)) {
			if (title.length > 12) {
				title = title.slice(0, 12);
			}
			rrk[key] = {"epi": epi, "tit": title, "tim": tim, "bmk": bmk};
		}
	}

	// Download all switch
	function DAS() {
		sdb = !sdb;
		const eee = sdb ? "On" : "Off";
		alert("Download all episodes is " + eee);
		USV();
	}
	// 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(title, nlc);
			alert("File downloads completed");
		})
		.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(title, nlc);
		alert("File downloads completed");
	}
	// 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(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 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);
	}

	// 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 = tbl[0], posy = tbl[1], diff = tbl[3];
		BCR("nse_fcb", "💠", "right: " + posx + "em;", "bottom: " + posy + "em;", "");
		if (auh) {
			BCR("nse_rhb", "🕓", "right: " + (posx - diff) + "em;", "bottom: " + posy + "em;", "none");
			BCR("nse_shb", "💾", "right: " + (posx - diff) + "em;", "bottom: " + (posy + diff) + "em;", "none");
		}
		switch (true) {
			case uRi.includes("/antenna/works"):
			case uRi.includes("/favnovelmain/"):
				BCR("nse_asb", "💟", "right: " + posx + "em;", "bottom: " + (posy - diff) + "em;", "none");
				BCR("nse_csb", "🎨", "right: " + (posx + diff) + "em;", "bottom: " + (posy - diff) + "em;", "none");
				BCR("nse_urb", "🔢", "right: " + (posx + diff) + "em;", "bottom: " + posy + "em;", "none");
				if (auh) {
					BCR("nse_ihb", "💕", "right: " + posx + "em;", "bottom: " + (posy + diff) + "em;", "none");
				}
				break;
			case uRi.includes("_histor"):
			case uRi.includes("/rireki"):
				if (auh) {
					BCR("nse_ihb", "💕", "right: " + posx + "em;", "bottom: " + (posy + diff) + "em;", "none");
				}
				break;
			case /^https:\/\/ncode\.syosetu\.com\/[A-z0-9]+/.test(uRi):
			case /^https:\/\/kakuyomu\.jp\/works\/[0-9]+/.test(uRi):
				if (sdb) {
					BCR("nse_dlb", "📥", "right: " + (posx + diff) + "em;", "bottom: " + (posy + diff) + "em;", "none");
				}
			break;
		}
		document.getElementById("nse_fcb").addEventListener("click", SCB, true);
	}
	// Button positon tweak
	function BPS() {
		const cfcb = document.getElementById("nse_fcb");
		const cfcm = document.getElementById("nse_fcm");
		if (!sFe) {
			sFe = true;
			cfcb.removeEventListener("click", SCB, true);
			cfcm.removeEventListener("click", FBH, true);
			const bno = document.getElementsByClassName("nse_cfb");
			let bi = bno.length - 1;
			while (bi >= 0) {
				bno[bi].remove();
				--bi;
			}
			const posx = tbl[0], posy = tbl[1], diff = tbl[3];
			BCR("nse_ptu", "⏫", "right: " + posx + "em;", "bottom: " + (posy + diff) + "em;", "");
			BCR("nse_ptd", "⏬", "right: " + posx + "em;", "bottom: " + (posy - diff) + "em;", "");	
			BCR("nse_ptl", "⏪", "right: " + (posx + diff) + "em;", "bottom: " + posy + "em;", "");
			BCR("nse_ptr", "⏩", "right: " + (posx - diff) + "em;", "bottom: " + posy + "em;", "");
			cfcm.addEventListener("click", PTH, true);
		} else {
			sFe = sFb = false;
			cfcm.removeEventListener("click", PTH, true);
			cfcb.remove();
			const tno = document.getElementsByClassName("nse_cft");
			let ti = tno.length - 1;
			while (ti >= 0) {
				tno[ti].remove();
				--ti;
			}
			USV();
			CFB();
		}
	}
	// Position tweak handler
	function PTH(e) {
		const dist = tbl[2];
		switch (e.target.id) {
			// Reset
			case "nse_fcb": 
				tbl = [4, 4, 1, 2];
				break;
			// Up
			case "nse_ptu":
				tbl[1] = tbl[1] + dist;
				break;
			// Down
			case "nse_ptd":
				tbl[1] = tbl[1] - dist;
				break;
			// Left
			case "nse_ptl":
				tbl[0] = tbl[0] + dist;
				break;
			// Right
			case "nse_ptr":
				tbl[0] = tbl[0] - dist;
				break;
			default:
				//console.log("DoNothing");
		}
		const cfcb = document.getElementById("nse_fcb");
		cfcb.style.right = tbl[0] + "em";
		cfcb.style.bottom = tbl[1] + "em";
	}
	// Show child button
	function SCB() {
		if (!sFb) {
			sFb = true;
			document.getElementById("nse_fcm").addEventListener("click", FBH, true);
		} else {
			sFb = false;
			document.getElementById("nse_fcm").removeEventListener("click", FBH, true);
		}
		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) {
			// Author select button
			case "nse_asb":
				ADA();
				break;
			// Colour setting button
			case "nse_csb":
				SUC();
				break;
			// Unreads setting button
			case "nse_urb":
				SUN();
				break;
			// Reading history button
			case "nse_rhb": {
				if (!document.getElementById("nse_rhp")) {
					const cDiv = document.body.appendChild(document.createElement("div"));
					cDiv.id = "nse_rhp";
					const cPos = !rMb ? " width: 50%; left: 25%;" : " width: 98%; left: 1%;";
					cDiv.style = "position: fixed;" + cPos + " overflow-y: scroll; height: 52%; top:10%; z-index: 9999; background-color: #f1f3f5; display: none;";
				}
				const crhp = document.getElementById("nse_rhp");
				if (!sFd) {
					sFd = 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, true);
				} else {
					sFd = false;
					crhp.style.display = "none";
					// Clear history
					crhp.innerHTML = "";
					crhp.removeEventListener("click", RHB, true);
					USV();
				}
				break;
			}
			// Save history button
			case "nse_shb": {
				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(rrk).reverse().forEach(k => {
					const vbmk = rrk[k].bmk === "1" ? " 💖 "
								: rrk[k].bmk === "2" ? " 🖤 "
								: " ❓ ";
					let kpt = "";
					let vlink = upa + k + upb + rrk[k].epi;
					if (gMk === "HUN_K") {
						if (rrk[k].epi.length <= 4) {
							kpt = "[" + rrk[k].epi + "]";
							vlink = upa + k + "/resume_reading";
						}
					}
					htxt += rrk[k].tim + vbmk + rrk[k].tit.replace(/\n/g,'') + kpt + ": " + vlink + "\n";
				});
				SAT(htit, htxt);
				alert("File downloads completed");
				break;
			}
			// Import history button
			case "nse_ihb":
				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");
					}
				}
				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");
		}
		return false;
	}
	// Reading history button
	function RHB(e) {
		if (e.target.classList.contains("nse_drh")) {
			// Delete history button
			const key = e.target.getAttribute("data");
			delete rrk[key];
			CRH();
		} else if (e.target.classList.contains("nse_brh")) {
			// Bookmark history button
			const key = e.target.getAttribute("data");
			rrk[key].bmk = rrk[key].bmk === "1" ? "2" : "1";
			CRH();
		}
	}
	// Button creater
	function BCR(id, text, posx, posy, show) {
		const cButton = document.getElementById("nse_fcm").appendChild(document.createElement("button"));
		cButton.id = id;
		if (id === "nse_fcb") {
			//console.log("💠");
		} else if (sFe) {
			cButton.classList.add("nse_cft");
		} else {
			cButton.classList.add("nse_cfb");
		}
		cButton.textContent = text;
		cButton.style = "position: fixed; width: 44px; height: 44px; z-index: 9999; font-size: 200%; opacity: 50%; cursor:pointer; border: none; padding: unset;" + posx + posy;
		cButton.type = "button";
		cButton.style.display = show;
	}
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址