FanfictionQomplete

Loads all following chapters on fanfiction.net and strips off bloat.

Pada tanggal 15 Juli 2015. Lihat %(latest_version_link).

// ==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.20150715163242
// ==/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, '<span style="color:hsl(' + d + ' ,100%, 50%);">$1</span>')
      //     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 ? chap_select.children.length : 1
  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)

  // function goGrey() {
  //   var wraps = document.getElementsByClassName('wrap')
  //   for (i = 0; i < wraps.length; i += 1) {
  //     wraps[i].style.background = '#888'
  //     wraps[i].style.backgroundColor = '#888'
  //   }
  //   // console.log("Q")
  //   // // console.log("this", this)
  //   // this.style.background = '#888'
  //   // this.style.backgroundColor = '#888'
  // }

  // document.addEventListener("dblclick", goGrey)

  var loadwrap = document.createElement('div')
  loadwrap.setAttribute('class', 'wrap')
  loadwrap.style.position = 'sticky'
  loadwrap.style.top = '0px'
  var loading = document.createElement('div')
  loading.style.float = 'left'
  loading.setAttribute('id', 'loading')
  // loading.style.margin = '0px'
  // loading.innerHTML = String.fromCharCode(160)
  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, appendedNow, notAppendedYet, 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 = 'Right'\n\
          } else {\n\
            e.remove()\n\
            document.getElementById('posbtn').innerHTML = 'Centered'\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 = 'Left'\n\
        }\n\
      }")
    posbtn.setAttribute('style', 'float:left;')
    posbtn.innerHTML = 'Centered'

    var bgcolbtn = document.createElement('button')
    bgcolbtn.setAttribute('id', 'bgcolbtn')
    bgcolbtn.setAttribute('onclick',
      "{\n\
        var e = document.getElementById('bgcol-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('bgcolbtn').innerHTML = 'Background: Black'\n\
          } else {\n\
            e.remove()\n\
            document.getElementById('bgcolbtn').innerHTML = 'Background: Dark'\n\
          }\n\
        } else {\n\
          var s = document.createElement('style')\n\
          s.setAttribute('id', 'bgcol-style')\n\
          s.innerHTML = 'body{color:#000;}a:hover{color:#000;}div.pad{background-color:#fff;}'\n\
          document.head.appendChild(s)\n\
          document.getElementById('bgcolbtn').innerHTML = 'Background: White'\n\
        }\n\
      }")

    bgcolbtn.setAttribute('style', 'float:left;')
    bgcolbtn.innerHTML = 'Background: Dark'

    var edgebtn = document.createElement('button')
    edgebtn.setAttribute('id', 'edgebtn')
    edgebtn.setAttribute('onclick',
      "{\n\
        var e = document.getElementById('edge-style')\n\
        if (e) {\n\
          e.remove()\n\
          document.getElementById('edgebtn').innerHTML = 'Edge: Rainbow'\n\
        } else {\n\
          var s = document.createElement('style')\n\
          s.setAttribute('id', 'edge-style')\n\
          s.innerHTML = '.col0, .col1, .col2, .col3, .col4, .col5{background-color:#333;background:#333;}.profile{background-color:#333;background:linear-gradient(to bottom, #fff, #333);}'\n\
          document.head.appendChild(s)\n\
          document.getElementById('edgebtn').innerHTML = 'Edge: Gray'\n\
        }\n\
      }")

    edgebtn.setAttribute('style', 'float:left;')
    edgebtn.innerHTML = 'Edge: Rainbow'

    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 = 'Width: Narrow'\n\
          } else {\n\
            e.remove()\n\
            document.getElementById('widthbtn').innerHTML = '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 = 'Width: Wide'\n\
        }\n\
      }")
    widthbtn.setAttribute('class', 'center')
    widthbtn.innerHTML = 'Width: Default'

    panel.appendChild(widthbtn)
    panel.appendChild(posbtn)
    panel.appendChild(bgcolbtn)
    panel.appendChild(edgebtn)
  }

  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()