// ==UserScript== // @name FanfictionQomplete // @description Loads all following chapters on fanfiction.net and strips off bloat. // @namespace https://greasyfork.org/en/users/11891-qon // @author Qon // @include https://www.fanfiction.net/s/*/* // @noframes // @grant none // @license Simple Public License 2.0 (SimPL) https://tldrlegal.com/license/simple-public-license-2.0-%28simpl%29 // @version 0.0.1.20150713152553 // @downloadURL none // ==/UserScript== // javascript:var script=document.createElement("script");var t=new Date(Date());script.src="https://greasyfork.org/en/scripts/10182-fanfictionqomplete/code/fanfictionqomplete.js?"+t.getFullYear()+t.getMonth()+t.getDate();document.body.appendChild(script);window.setTimeout(function(){document.runFFQomplete();},500); if (Element.prototype.remove == undefined) { Element.prototype.remove = function() { this.parentNode.removeChild(this) } } function injectRunButton() { var lc = document.getElementsByClassName('lc') if (lc.length) { lc = lc[0] var btn = document.createElement('button') btn.setAttribute('onclick', 'document.runFFQomplete();') btn.setAttribute('class', 'btn') btn.setAttribute('style', 'margin-left:12px;margin-right:2px;') btn.setAttribute('title', 'Append all following chapters and remove unecessary bloat.') btn.innerHTML = 'Qomplete!' lc.appendChild(btn) } } injectRunButton() // document.styleSheets[0].cssText = ""; document.runFFQomplete = function() { window.onload = function() { var a = document.getElementsByClassName('skiptranslate') for (; a.length;) { a[0].remove() } var c = document.body.style.backgroundColor document.body.removeAttribute('style') document.body.style.backgroundColor = c // data race but whatever. } var re = /(^.*?fanfiction\.net\/s\/\d+\/)(\d+)(\/?[^#]*)/ function urlGetChap(url) { var arr = re.exec(url) return arr[2] } function urlSetChap(url, n) { var arr = re.exec(url) return arr[1] + n + arr[3] } function inc(url) { var arr = re.exec(url) return arr[1] + (parseInt(arr[2]) + 1) + arr[3] } function chapFromPage(url, page) { var storytext = page.getElementById('storytext') if (storytext) { // var ps = storytext.getElementsByTagName('p') // var d = 0 // for (q of ps) { // q.style.color = 'hsl(' + d + ' ,20%, 80%)' // // q.innerHTML = q.innerHTML.replace(/([\.,?!])/g, '$1') // d = (d + 1 / ps.length * 360) % 360 // } var wrap = page.createElement('div') wrap.setAttribute('class', 'wrap col' + (urlGetChap(url) % 6)) wrap.setAttribute('id', urlGetChap(url)) var pad = page.createElement('div') pad.setAttribute('class', 'pad') var chapdiv = page.createElement('div') chapdiv.setAttribute('class', 'chapter') var chapspan = page.createElement('span') chapspan.innerHTML = urlGetChap(url) + '. ' var title = page.getElementsByTagName('title')[0] var chaptitle = page.createElement('a') chaptitle.setAttribute('href', url.replace(/#.*$/, "")) chaptitle.setAttribute('class', 'external') chaptitle.innerHTML = title.innerHTML chapdiv.appendChild(chapspan) chapdiv.appendChild(chaptitle) chapdiv.appendChild(document.createElement('hr')) chapdiv.appendChild(storytext) pad.appendChild(chapdiv) wrap.appendChild(pad) return wrap } else return null } document.body.setAttribute('style', '') var title = document.getElementsByTagName('title')[0] var profile_top = document.getElementById('profile_top') var chap_select = document.getElementById('chap_select') var latestChap = chap_select.children.length var activeChap = parseInt(urlGetChap(document.location)) var appendedNow = 1 var notAppendedYet = 0 var chapArr = [] var chap = chapFromPage(document.location.href, document) var ptbuttons = profile_top.getElementsByTagName('button') if (ptbuttons.length) { ptbuttons[0].remove() for (; document.head.firstElementChild;) document.head.firstElementChild.remove(); for (; document.body.firstElementChild;) document.body.firstElementChild.remove(); } document.body.removeAttribute('style') var style = document.createElement('style') style.setAttribute('type', 'text/css') style.innerHTML = 'body{background-color:#000;color:#ccc;margin:0;padding:0;font-family:"Verdana";}\n\ #loading{position:inherit;width:100%;height:5px;}\n\ button, select{border-radius:4px;padding:4px 12px;background: linear-gradient(to bottom, #333, #000);border-width: 1px;color:#ccc;background-color:#000;}\n\ button:hover{background-image:none;}\n\ .panel{text-align:center;}\n\ a.external, option.external{background: transparent url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGXRFWHRTb2Z0d2FyZQBBZ\ G9iZSBJbWFnZVJlYWR5ccllPAAAAFZJREFUeF59z4EJADEIQ1F36k7u5E7ZKXeUQPACJ3wK7UNokVxVk9kHnQH7bY9hbDyDhNXgjpRLqFlo4M2GgfyJHhjq8V4agfrgPQX3JtJQGbofmCHgA/nAKks+JAjFA\ AAAAElFTkSuQmCC") no-repeat scroll right center;padding-right: 13px;}\n\ div.wrap{max-width:1300px;margin:auto;padding:0px 5px 0px 5px;}\n\ div.wrap:nth-child(2){padding-top:5px;margin-top:50px;}\n\ div.wrap:last-child{padding-bottom:5px;margin-bottom:50px;}\n\ div.pad{background-color:#222;padding:50px;}\n\ .chapter{}#profile_top{}img{float:left;}canvas{float:left;}\n\ a:link{color:#a05;}a:visited{color:#555;}a:hover{color:#fff;}a:active{color:#a05;}\n\ .col1{background-color:#f00;background:linear-gradient(to bottom, #f00, #ff0);}\n\ .col2{background-color:#ff0;background:linear-gradient(to bottom, #ff0, #0f0);}\n\ .col3{background-color:#0f0;background:linear-gradient(to bottom, #0f0, #0ff);}\n\ .col4{background-color:#0ff;background:linear-gradient(to bottom, #0ff, #00f);}\n\ .col5{background-color:#00f;background:linear-gradient(to bottom, #00f, #f0f);}\n\ .col0{background-color:#f0f;background:linear-gradient(to bottom, #f0f, #f00);}\n' switch ((urlGetChap(document.location.href) % 6)) { case 0: style.innerHTML += '.profile{background-color:#777;background:linear-gradient(to bottom, #fff, #f0f);}\n' break; case 1: style.innerHTML += '.profile{background-color:#777;background:linear-gradient(to bottom, #fff, #f00);}\n' break; case 2: style.innerHTML += '.profile{background-color:#777;background:linear-gradient(to bottom, #fff, #ff0);}\n' break; case 3: style.innerHTML += '.profile{background-color:#777;background:linear-gradient(to bottom, #fff, #0f0);}\n' break; case 4: style.innerHTML += '.profile{background-color:#777;background:linear-gradient(to bottom, #fff, #0ff);}\n' break; case 5: style.innerHTML += '.profile{background-color:#777;background:linear-gradient(to bottom, #fff, #00f);}\n' break; } document.head.appendChild(style) document.head.appendChild(title) var loadwrap = document.createElement('div') loadwrap.setAttribute('class', 'wrap') loadwrap.style.position = 'sticky' loadwrap.style.top = '0px' loadwrap.style.maxWidth = '1300px' loadwrap.style.backgroundColor = 'red' var loading = document.createElement('div') loading.style.float = 'left' loading.setAttribute('id', 'loading') // loading.innerHTML = String.fromCharCode(160) // loading.style.marginTop = '50px' // loading.style.marginLeft = '5px' // loading.style.marginRight = '5px' // loading.style.padding = '5px 0 0 0' loadwrap.appendChild(loading) document.body.appendChild(loadwrap) function updateLoading(ignore, appended, downloaded, total) { // var loading = document.getElementById('loading') var p0 = parseInt(ignore / total * 100) var p1 = parseInt((appended + ignore) / total * 100) if (p1 == 100) { setTimeout(function() { loading.style.display = 'none' }, (activeChap != latestChap) * 100) } var p2 = parseInt((downloaded + appended + ignore) / total * 100) loading.style.background = 'linear-gradient(to right' + ', white 0%' + ', white ' + p0 + '%, lime ' + p0 + '%, lime ' + p1 + '%, blue ' + p1 + '%, blue ' + p2 + '%, white ' + p2 + '%, white 100%)' } updateLoading(activeChap - 1, 0, 0, latestChap) var profile = document.createElement('div') profile.setAttribute('class', 'wrap profile') profile.setAttribute('id', 'profile') var pad = document.createElement('div') pad.setAttribute('class', 'pad') pad.appendChild(profile_top) profile.appendChild(pad) document.body.appendChild(profile) var panel = document.createElement('div') panel.setAttribute('class', 'panel') { var posbtn = document.createElement('button') posbtn.setAttribute('id', 'posbtn') posbtn.setAttribute('onclick', "{\n\ var e = document.getElementById('position-style')\n\ if (e) {\n\ if (e.innerHTML == 'div.wrap{margin-left:0px;}') {\n\ e.innerHTML = 'div.wrap{margin-right:0px;}'\n\ document.getElementById('posbtn').innerHTML = 'Position: Right'\n\ } else {\n\ e.remove()\n\ document.getElementById('posbtn').innerHTML = 'Position: Center'\n\ }\n\ } else {\n\ var s = document.createElement('style')\n\ s.setAttribute('id', 'position-style')\n\ s.innerHTML = 'div.wrap{margin-left:0px;}'\n\ document.head.appendChild(s)\n\ document.getElementById('posbtn').innerHTML = 'Position: Left'\n\ }\n\ }") posbtn.setAttribute('style', 'float:left;') posbtn.innerHTML = 'Position: Center' var colschemebtn = document.createElement('button') colschemebtn.setAttribute('id', 'colschemebtn') colschemebtn.setAttribute('onclick', "{\n\ var e = document.getElementById('colscheme-style')\n\ if (e) {\n\ if (e.innerHTML == 'body{color:#000;}a:hover{color:#000;}div.pad{background-color:#fff;}') {\n\ e.innerHTML = 'body{color:#fff;}div.pad{background-color:#000;}'\n\ document.getElementById('colschemebtn').innerHTML = 'Color Scheme: Black'\n\ } else {\n\ e.remove()\n\ document.getElementById('colschemebtn').innerHTML = 'Color Scheme: Dark'\n\ }\n\ } else {\n\ var s = document.createElement('style')\n\ s.setAttribute('id', 'colscheme-style')\n\ s.innerHTML = 'body{color:#000;}a:hover{color:#000;}div.pad{background-color:#fff;}'\n\ document.head.appendChild(s)\n\ document.getElementById('colschemebtn').innerHTML = 'Color Scheme: White'\n\ }\n\ }") colschemebtn.setAttribute('style', 'float:left;') colschemebtn.innerHTML = 'Color Scheme: Dark' var widthbtn = document.createElement('button') widthbtn.setAttribute('id', 'widthbtn') widthbtn.setAttribute('onclick', "{\n\ var e = document.getElementById('width-style')\n\ if (e) {\n\ if (e.innerHTML == 'div.wrap{max-width:100%;}') {\n\ e.innerHTML = 'div.wrap{max-width:777px;}'\n\ document.getElementById('widthbtn').innerHTML = 'Page Width: Narrow'\n\ } else {\n\ e.remove()\n\ document.getElementById('widthbtn').innerHTML = 'Page Width: Default'\n\ }\n\ } else {\n\ var s = document.createElement('style')\n\ s.setAttribute('id', 'width-style')\n\ s.innerHTML = 'div.wrap{max-width:100%;}'\n\ document.head.appendChild(s)\n\ document.getElementById('widthbtn').innerHTML = 'Page Width: Wide'\n\ }\n\ }") widthbtn.setAttribute('class', 'center') widthbtn.innerHTML = 'Page Width: Default' panel.appendChild(widthbtn) panel.appendChild(posbtn) panel.appendChild(colschemebtn) } if (chap_select) { chap_select.setAttribute('onchange', 'if(this.options[this.selectedIndex].value < ' + urlGetChap(document.location.href) + '){' + chap_select.getAttribute('onchange') + '}' + ' else {document.getElementById(\'\'+this.options[this.selectedIndex].value).scrollIntoView();}' ) chap_select.setAttribute('style', 'float:right;') var os = chap_select.getElementsByTagName('option') for (i = 0; i < urlGetChap(document.location) - 1; i += 1) { os[i].setAttribute('class', 'external') } panel.appendChild(chap_select) } document.getElementById('profile').firstChild.appendChild(panel) // document.body.insertBefore(panel, document.body.firstChild) document.body.appendChild(chap) function loadQomplete() { var a = document.getElementsByClassName('skiptranslate') for (; a.length;) { a[0].remove() } document.body.removeAttribute('style') updateLoading(activeChap - 1, latestChap - (activeChap - 1), 0, latestChap) // console.log("qomplete", performance.now()) } function appendChapterFromURL(url) { var oReq = new XMLHttpRequest(); oReq.onload = function() { var xmlDoc = new DOMParser().parseFromString(this.responseText, "text/html") var url = this.responseURL ? this.responseURL : this.responseURLfallback var chap = chapFromPage(url, xmlDoc) if (chap) { document.body.appendChild(chap) appendedNow += 1 updateLoading(activeChap - 1, appendedNow, notAppendedYet, latestChap) window.setTimeout(function() { appendChapterFromURL(inc(url)) }, 0) } else loadQomplete() } oReq.responseURLfallback = url oReq.open("get", url, true) oReq.send() } function appendChapterFromURL2(url) { var oReq = new XMLHttpRequest(); oReq.onload = function() { var xmlDoc = new DOMParser().parseFromString(this.responseText, "text/html") var url = this.responseURL ? this.responseURL : this.responseURLfallback var chap = chapFromPage(url, xmlDoc) if (chap) { chapArr[parseInt(urlGetChap(url))] = chap notAppendedYet += 1 updateLoading(activeChap - 1, appendedNow, notAppendedYet, latestChap) if (appendedNow + notAppendedYet + activeChap - 1 == latestChap) { // console.log("all downloaded", performance.now()) } } } oReq.responseURLfallback = url oReq.open("get", url, true) oReq.send() } function appendNextChap(n) { if (chapArr[n]) { document.body.appendChild(chapArr[n]) appendedNow += 1 notAppendedYet -= 1 updateLoading(activeChap - 1, appendedNow, notAppendedYet, latestChap) if (n < latestChap) { appendNextChap(n + 1) } else { loadQomplete() } } else { window.setTimeout(function() { appendNextChap(n) }, 50) } } if (true) { // Asynchronous chapter load. Very fast for big fanfics. for (i = activeChap; i < latestChap; i += 1) appendChapterFromURL2(urlSetChap(document.location.href, i + 1)); if (activeChap == latestChap) { loadQomplete() } else { appendNextChap(activeChap + 1) } } else { // Synchronous chapter load. Slow for big fanfics but doesn't hit the server as hard. appendChapterFromURL(inc(document.location.href)) } } // document.runFFQomplete()