diff --git a/plugin/js/Util.js b/plugin/js/Util.js index ae941401..93467100 100644 --- a/plugin/js/Util.js +++ b/plugin/js/Util.js @@ -1069,6 +1069,22 @@ const util = (function() { } } + function rot13(str) { + let result = ""; + + for (let i = 0; i < str.length; i++) { + const code = str.charCodeAt(i); + if (code >= 65 && code <= 90) { + result += String.fromCharCode((code - 65 + 13) % 26 + 65); + } else if (code >= 97 && code <= 122) { + result += String.fromCharCode((code - 97 + 13) % 26 + 97); + } else { + result += str[i]; + } + } + return result; + } + function sanitize(dirty) { let savedBaseURI = dirty.baseURI; const clean = DOMPurify.sanitize(dirty); @@ -1262,6 +1278,7 @@ const util = (function() { replaceSemanticInlineStylesWithTags: replaceSemanticInlineStylesWithTags, wrapInnerContentInTag: wrapInnerContentInTag, getDefaultExtensionByMime: getDefaultExtensionByMime, - detectMimeType: detectMimeType + detectMimeType: detectMimeType, + rot13: rot13 }; })(); diff --git a/plugin/js/parsers/FictioneerParser.js b/plugin/js/parsers/FictioneerParser.js index 164ec1eb..f719cd51 100644 --- a/plugin/js/parsers/FictioneerParser.js +++ b/plugin/js/parsers/FictioneerParser.js @@ -112,18 +112,49 @@ class FictioneerParser extends Parser { } preprocessRawDom(chapterDom) { - let antiScrape = chapterDom.querySelector(".tiv-anti-scrape")?.parentNode; - if (!antiScrape) return; + this.processGhostContent(chapterDom); - let payloadEl = antiScrape.querySelector("script"); + const payloadHost = chapterDom.querySelector(".tiv-anti-scrape")?.parentNode; + if (!payloadHost) return; + + const payloadEl = payloadHost.querySelector("script"); if (!payloadEl) return; - let data = JSON.parse(payloadEl.textContent || payloadEl.innerText || "{}"); - antiScrape.replaceChildren(); - let cryptNode = chapterDom.createElement("p"); - cryptNode.className = "encryptedPayload"; - cryptNode.textContent = data.data; - antiScrape.appendChild(cryptNode); + const data = JSON.parse(payloadEl.textContent || payloadEl.innerText || "{}"); + payloadHost.replaceChildren(); + const payloadNode = chapterDom.createElement("p"); + payloadNode.className = "obfuscatedPayload"; + payloadNode.textContent = data.data; + payloadHost.appendChild(payloadNode); + } + + processGhostContent(chapterDom) { + const ghostScript = chapterDom.querySelector("script[data-poly]"); + if (!ghostScript) return; + + const poly = ghostScript.getAttribute("data-poly"); + const total = parseInt(ghostScript.getAttribute("data-total") || "0", 10); + if (!poly || total <= 0) return; + + const encoded = Array.from({ length: total }, (_, i) => + ghostScript.getAttribute(`data-${poly}-${i}`) || "" + ).join(""); + if (!encoded) return; + + let plain; + try { + plain = decodeURIComponent(atob(util.rot13(encoded))); + } catch { + return; + } + + const host = chapterDom.querySelector("#cherry-content-host"); + if (host) { + const temp = chapterDom.createElement("div"); + temp.innerHTML = plain; + host.replaceWith(...temp.childNodes); + } + ghostScript.remove(); } customRawDomToContentStep(chapter, content) {