Adds "Download All Chapters" button to Royal Road fictions
目前為
// ==UserScript== // @name Royal Road Download Button // @license MIT // @namespace rtonne // @match https://www.royalroad.com/fiction/* // @exlcude https://www.royalroad.com/fiction/*/chapter/* // @grant none // @version 1.0 // @author Rtonne // @description Adds "Download All Chapters" button to Royal Road fictions // @require https://cdn.jsdelivr.net/npm/[email protected] // @require https://cdn.jsdelivr.net/npm/[email protected] // ==/UserScript== const row = document.createElement("div"); row.className = "row reduced-gutter"; const col = document.createElement("div"); col.className = "col-xs-12"; row.appendChild(col); const button = document.createElement("button"); button.className = "button-icon-large"; col.appendChild(button); const buttonStyle = getComputedStyle(button); const progressBar = document.createElement("div"); progressBar.style.position = "absolute"; progressBar.style.top = "48px"; progressBar.style.right = "0"; progressBar.style.marginRight = "15px"; progressBar.style.height = "2px"; progressBar.style.background = "#67809f"; progressBar.style.width = "0"; progressBar.className = "RRScraperProgressBar"; col.appendChild(progressBar); const i = document.createElement("i"); i.className = "fa fa-download"; button.appendChild(i); const span = document.createElement("span"); span.innerText = "Download All Chapters"; span.className = "center"; button.appendChild(span); const defaultButtonRows = document.querySelectorAll("div.row.reduced-gutter"); defaultButtonRows.forEach((defaultButtonRow) => { const rowClone = row.cloneNode(true); rowClone.onclick = () => { download(); }; defaultButtonRow.insertAdjacentElement("afterend", rowClone); }); async function download() { document.querySelectorAll("div.RRScraperProgressBar").forEach(element => { element.style.width = "calc(100% - 30px)"; }); const parser = new DOMParser(); const zip = new JSZip(); const fictionName = window.location.href.split("/")[window.location.href.split("/").length - 1]; var newHtml = await fetch(window.location.href, {credentials: 'omit'}) .then(response => response.text()) .then(text => parser.parseFromString(text, "text/html")); const chapterUrls = [...newHtml.querySelectorAll("tr.chapter-row")].map(element => { return element.getAttribute("data-url"); }); const chapterCount = chapterUrls.length; const chapterCountLength = chapterCount.toString().length; const fillZeros = "0".repeat(chapterCountLength); // timeoutLoop for the progress bar to work let index = 0; async function timeoutLoop() { let chapterUrl = chapterUrls[index]; newHtml = await fetch("https://www.royalroad.com" + chapterUrl, {credentials: 'omit'}) .then(response => response.text()) .then(text => parser.parseFromString(text, "text/html")); let chapter = newHtml.querySelector("div.fic-header > div > div.col-lg-6").outerHTML; [...newHtml.querySelector("div.chapter-content").parentNode.children].forEach(element => { if (element.classList.contains("chapter-content") || element.classList.contains("author-note-portlet")) { chapter += "\n" + element.outerHTML; } }) let chapterName = chapterUrl.split("/")[chapterUrl.split("/").length - 1]; let filledIndex = (fillZeros + (index + 1)).slice(chapterCountLength * -1); zip.file(fictionName + "/" + filledIndex + "_" + chapterName, chapter); document.querySelectorAll("div.RRScraperProgressBar").forEach(element => { element.style.width = "calc(" + ((chapterCount - index) / chapterCount) * 100 + "% - 30px)"; element.style.display = "none"; element.style.display = "block"; }); if (++index < chapterCount) { setTimeout(timeoutLoop, 0); } else { zip.generateAsync({type: "blob"}).then((blob) => { saveAs(blob, fictionName + ".zip"); }); } } setTimeout(timeoutLoop, 0); }