// ==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));
}
})();