Greasy Fork

FanfictionQomplete

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

目前为 2015-07-26 提交的版本。查看 最新版本

// ==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/*/*
// @compatible    firefox
// @compatible    chrome
// @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.20150726044616
// ==/UserScript==

/*
TODO
  Add support for other sites
    https://www.fictionpress.com
      Already done, but I haven't put this site among the include rules for now. Don't know if anyone cares about it...
      If you want it you can add "https://www.fanfiction.net/s/* /*" (without "" and without the space) to the inclusion rules yourself in the script settings
    fimfiction.com
      Will take more work but it's more popular site and I do read fics there myself sometimes.
  Change width by dragging the border? and position?
  Convert images to data url.
  Automatically qomplete fanfics?
    Not sure it's a good idea, even as an option for several reasons.
    Might crash browsers at startup, strain servers a whole lot and make it harder to access features like chapters reviewing, fav and follow fics and other issues.
  Save settings?
    Like background color and page with, so that you don't have to change them every time to your preferences.
    Not sure if this should be implemented or not. If fics are automatically qompleted this would be needed for brosers to remember your previous scroll position.
*/

// 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()
    }
    document.body.removeAttribute('style')
  }

  var re = /(^.*?(?:fan|fim)?fiction(?:press\.com|\.net)\/s(?:tory)?\/\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')
      var cut = /(.*(| [Cc]hapter [^:]+: .*)), a .* fanfic \| FanFiction/
      var newChapTitle = cut.exec(title.innerHTML)
      chaptitle.innerHTML = newChapTitle ? newChapTitle[1] : 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 statusCompleteRE = /Status: Complete/
  var statusComplete = statusCompleteRE.exec(profile_top.innerHTML) ? true : false
  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 favicon = [].slice.call(document.head.getElementsByTagName('link')).slice(2,5)

  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:#555;}a:visited{color:#555;}a:hover{color:#aaa;}a:active{color:#aaa;}\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)
  var settitle = /(.*)[Cc]hapter .*, a (.*) fanfic \| FanFiction/
  var storyname = settitle.exec(title.innerHTML)
  if(!storyname) {
    var settitle = /(.*), a (.*) fanfic \| FanFiction/
    var storyname = settitle.exec(title.innerHTML)
  }
  if(storyname) {
    title.innerHTML = storyname[1]+' - '+storyname[2]+(statusComplete && activeChap==1 ? ' - Qomplete' : (' - chapter '+activeChap+(latestChap!=activeChap?'-'+latestChap:'')))
  }
  document.head.appendChild(title)
  for (i=0;i<favicon.length;i+=1) {
    document.head.appendChild(favicon[i])
  }

  // 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:777px;}') {\n\
            e.innerHTML = 'div.wrap{max-width:100%;}'\n\
            document.getElementById('widthbtn').innerHTML = 'Width: Wide'\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:777px;}'\n\
          document.head.appendChild(s)\n\
          document.getElementById('widthbtn').innerHTML = 'Width: Narrow'\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()