// ==UserScript== // @name Novel Sites Enhance // @name:ja 小説サイト機能強化 // @namespace https://greasyfork.org/en/users/1264733 // @version 2024-08-04 // @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 // @downloadURL https://update.greasyfork.cloud/scripts/490265/Novel%20Sites%20Enhance.user.js // @updateURL https://update.greasyfork.cloud/scripts/490265/Novel%20Sites%20Enhance.meta.js // ==/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 = "a.p-up-bookmark-item__data-item"; 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 = `
Paste options here
📤 Import options from above
💾 Save options to local txt
`; 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 = '' + item + ': ' + tlo[bna] + pse;
} else if (index > 3 && index < 7) {
comu.innerHTML += psb + 'color:' + tlo[bna] + '; display: inline-block; min-width: 2em;" contenteditable>' + tlo[bna] + ' ✅ 🎨' + pse;
} else {
comu.innerHTML += psb + ' display: inline-block; min-width: 2em;" contenteditable>' + tlo[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 💕 Import from current page 💾 Save history to local txt ' +
'✖' +
'' + tlo.RRK[k].tim + '' +
'' + vbmk + '' +
vlink +
'閲覧履歴:
' +
cihb +
'