yande.re refine

yande.re refining

От 09.03.2020. Виж последната версия.

// ==UserScript==
// @name           yande.re refine
// @namespace      https://greasyfork.org/scripts/5250
// @description    yande.re refining
// @include        *://behoimi.org/*
// @include        *://www.behoimi.org/*
// @include        *://*.donmai.us/*
// @include        *://konachan.tld/*
// @include        *://yande.re/*
// @include        *://chan.sankakucomplex.com/*
// @version        2020.03.09
// @grant          none
// ==/UserScript==

//If true, each added page retains its paginator.  If false, elements are smoothly joined together.
var pageBreak = false

//Minimum amount of window left to scroll, maintained by loading more pages.
var scrollBuffer = 600

//Time (in ms) the script will wait for a response from the next page before attempting to fetch the page again.  If the script gets trapped in a loop trying to load the next page, increase this value.
var timeToFailure = 15000

var previewLocked = false

//============================================================================
//=========================Script initialization==============================
//============================================================================

var nextPage, mainTable, mainParent, pending, timeout, iframe

if (typeof customF != 'undefined') customF()

injectGlobalStyle()
initialize()

function initialize() {
  //Stop if inside an iframe
  if (window != window.top || scrollBuffer == 0) return

  //Stop if no "table"
  mainTable = getMainTable(document)
  if (!mainTable) return

  //Stop if no paginator
  var paginator = getPaginator(document)
  if (!paginator) return

  //Stop if no more pages
  nextPage = getNextPage(paginator)
  if (!nextPage) return

  injectStyle()
  hijackClick()
  processItems()

  //Hide the blacklist sidebar, since this script breaks the tag totals and post unhiding.
  var sidebar = document.getElementById('blacklisted-sidebar')
  if (sidebar) sidebar.style.display = 'none'

  //Other important variables:
  scrollBuffer += window.innerHeight
  mainParent = mainTable.parentNode
  pending = false

  iframe = document.createElement('iframe')
  iframe.width = iframe.height = 0
  iframe.style.visibility = 'hidden'
  document.body.appendChild(iframe)

  //Slight delay so that Danbooru's initialize_edit_links() has time to hide all the edit boxes on the Comment index
  iframe.addEventListener(
    'load',
    function(e) {
      setTimeout(appendNewContent, 100)
    },
    false
  )

  //Stop if empty page
  if (
    /<p>(Nothing to display.|Nobody here but us chickens!)<.p>/.test(
      mainTable.innerHTML
    )
  )
    return

  //Add copy of paginator to the top
  mainParent.insertBefore(paginator.cloneNode(true), mainParent.firstChild)

  if (!pageBreak) paginator.style.display = 'none'
  //Hide bottom paginator
  else {
    //Reposition bottom paginator and add horizontal break
    mainTable.parentNode.insertBefore(
      document.createElement('hr'),
      mainTable.nextSibling
    )
    mainTable.parentNode.insertBefore(paginator, mainTable.nextSibling)
  }

  //Listen for scroll events
  window.addEventListener('scroll', testScrollPosition, false)
  testScrollPosition()
}

//============================================================================
//============================Script functions================================
//============================================================================

//Some pages match multiple "tables", so order is important.
function getMainTable(source) {
  //Special case: Sankaku post index with Auto Paging enabled
  if (
    /sankaku/.test(location.host) &&
    /auto_page=1/.test(document.cookie) &&
    /^(post(\/|\/index\/?)?|\/)$/.test(location.pathname)
  )
    return null

  var xpath = [
    ".//div[@id='c-favorites']//div[@id='posts']", // Danbooru (/favorites)
    ".//div[@id='posts']/div", // Danbooru; don't want to fall through to the wrong xpath if no posts ("<article>") on first page.
    ".//div[@id='c-pools']//section/article/..", // Danbooru (/pools/####)

    ".//div[@id='a-index']/table[not(contains(@class,'search'))]", // Danbooru (/forum_topics, ...), take care that this doesn't catch comments containing tables
    ".//div[@id='a-index']", // Danbooru (/comments, ...)

    ".//table[contains(@class,'highlight')]", // large number of pages
    ".//div[@id='content']/div/div/div/div/span[@class='author']/../../../..", // Sankaku: note search
    ".//div[contains(@id,'comment-list')]/div/..", // comment index
    ".//*[not(contains(@id,'popular'))]/span[contains(@class,'thumb')]/a/../..", // post/index, pool/show, note/index
    ".//li/div/a[contains(@class,'thumb')]/../../..", // post/index, note/index
    ".//div[@id='content']//table/tbody/tr[@class='even']/../..", // user/index, wiki/history
    ".//div[@id='content']/div/table", // 3dbooru user records
    ".//div[@id='forum']" // forum/show
  ]

  for (var i = 0; i < xpath.length; i++) {
    getMainTable = (function(query) {
      return function(source) {
        var mTable = new XPathEvaluator().evaluate(
          query,
          source,
          null,
          XPathResult.FIRST_ORDERED_NODE_TYPE,
          null
        ).singleNodeValue
        if (!mTable || !pageBreak) return mTable

        //Special case: Danbooru's /favorites lacks the extra DIV that /posts has, which causes issues with the paginator/page break.
        var xDiv = document.createElement('div')
        xDiv.style.overflow = 'hidden'
        mTable.parentNode.insertBefore(xDiv, mTable)
        xDiv.appendChild(mTable)
        return xDiv
      }
    })(xpath[i])

    var result = getMainTable(source)
    if (result) {
      //alert("UPW main table query: "+xpath[i]+"\n\n"+location.pathname);
      return result
    }
  }

  return null
}

function getPaginator(source) {
  var pager = new XPathEvaluator().evaluate(
    "descendant-or-self::div[@id='paginator' or @class='paginator' or @id='paginater']",
    source,
    null,
    XPathResult.FIRST_ORDERED_NODE_TYPE,
    null
  ).singleNodeValue

  // Need clear:none to prevent the 2nd page from being pushed to below the sidebar on the Post index... but we don't want this when viewing a specific pool,
  // because then the paginator is shoved to the right of the last images on a page.  Other sites have issues with clear:none as well, like //yande.re/post.
  if (
    pager &&
    location.host.indexOf('donmai.') >= 0 &&
    document.getElementById('sidebar')
  )
    pager.style.clear = 'none'

  return pager
}

function getNextPage(source) {
  let page = getPaginator(source)
  if (page)
    page = new XPathEvaluator().evaluate(
      ".//a[@alt='next' or @rel='next' or contains(text(),'>') or contains(text(),'Next')]",
      page,
      null,
      XPathResult.FIRST_ORDERED_NODE_TYPE,
      null
    ).singleNodeValue

  return page && page.href
}

function testScrollPosition() {
  if (!nextPage) testScrollPosition = function() {}
  //Take the max of the two heights for browser compatibility
  else if (
    !pending &&
    window.pageYOffset + scrollBuffer >
      Math.max(
        document.documentElement.scrollHeight,
        document.documentElement.offsetHeight
      )
  ) {
    pending = true
    timeout = setTimeout(function() {
      pending = false
      testScrollPosition()
    }, timeToFailure)
    iframe.contentDocument.location.replace(nextPage)
  }
}

function appendNewContent() {
  //Make sure page is correct.  Using 'indexOf' instead of '!=' because links like "https://danbooru.donmai.us/pools?page=2&search%5Border%5D=" become "https://danbooru.donmai.us/pools?page=2" in the iframe href.
  clearTimeout(timeout)
  if (nextPage.indexOf(iframe.contentDocument.location.href) < 0) {
    setTimeout(function() {
      pending = false
    }, 1000)
    return
  }

  //Copy content from retrived page to current page, but leave off certain headers, labels, etc...
  var sourcePaginator = document.adoptNode(getPaginator(iframe.contentDocument))
  var nextElem,
    deleteMe,
    source = document.adoptNode(getMainTable(iframe.contentDocument))

  if (
    /<p>(Nothing to display.|Nobody here but us chickens!)<.p>/.test(
      source.innerHTML
    )
  )
    nextPage = null
  else {
    nextPage = getNextPage(sourcePaginator)

    if (pageBreak) mainParent.appendChild(source)
    else {
      //Hide elements separating one table from the next (h1 is used for user names on comment index)
      var rems = source.querySelectorAll('h2, h3, h4, thead, tfood')
      for (var i = 0; i < rems.length; i++) rems[i].style.display = 'none'

      //Move contents of next table into current one
      var fragment = document.createDocumentFragment()
      while ((nextElem = source.firstChild)) fragment.appendChild(nextElem)
      mainTable.appendChild(fragment)
    }
  }

  //Add the paginator at the bottom if needed.
  if (!nextPage || pageBreak) mainParent.appendChild(sourcePaginator)
  if (pageBreak && nextPage)
    mainParent.appendChild(document.createElement('hr'))

  //Clear the pending request marker and check position again
  processItems()
  pending = false
  testScrollPosition()
}

function injectGlobalStyle() {
  const s = document.createElement('style')
  s.innerHTML = `
#title { display: none !important; }
#header { margin: 0 !important; text-align: center; }
#header ul { float: none !important; display: inline-block;}
`
  document.body.appendChild(s)
}

function injectStyle() {
  const s = document.createElement('style')
  s.innerHTML = `
a.directlink { display: none !important; }
.sidebar { position: absolute; top: 10px; right: 10px; }
.sidebar #tag-sidebar, .sidebar h5 { display: none !important; }
div.content { width: 100vw; }
ul#post-list-posts { display: grid; grid-template-columns: 1fr 1fr 1fr 1fr; }
ul#post-list-posts li { width: auto !important; margin: auto; padding: 5px; transition: .3s ease-out; overflow: visible; }
ul#post-list-posts li img, ul#post-list-posts li .inner { width: auto !important; height: auto !important; }
ul#post-list-posts li img { border-radius: 5px; }
ul#post-list-posts li a { transition: .3s ease-out; }
ul#post-list-posts li a.liked img { border: 2px solid pink; }
ul#post-list-posts li:hover { transform: scale(1.3); z-index: 1;  }
ul#post-list-posts li:hover a { opacity: 1; box-shadow: 6px 6px 20px 0px rgba(50, 50, 50, 0.75); }
.hidden { display: none !important; }
.preivew-dialog { position: fixed; top: 0; left: 0; height: 100vh; width: 100vw; background: rgba(0,0,0,0.7); z-index: 100; }
.preivew-dialog iframe { position: absolute; height: 90vh; width: 80vw; top: 50%; left: 50%; transform: translate(-50%, -50%); background: grey; border: none; border-radius: 5px; overflow: hidden; }
    `
  document.body.appendChild(s)
}

function hijackClick() {}

let previewIframe, previewIframeDialog

function initPreviewIframe() {
  previewIframeDialog = document.createElement('div')
  previewIframeDialog.addClassName('preivew-dialog hidden')
  previewIframe = document.createElement('iframe')
  previewIframeDialog.appendChild(previewIframe)
  previewIframeDialog.onclick = e => {
    if (e.target === previewIframeDialog)
      previewIframeDialog.classList.toggle('hidden', true)
  }
  document.body.appendChild(previewIframeDialog)
}

function processItems() {
  document
    .querySelectorAll('ul#post-list-posts li a.thumb:not([_init])')
    .forEach(a => {
      a.setAttribute('_init', true)
      const url = a.href
      a.href = 'javascript:;'
      if (!url) return
      const id = url.split('/').slice(-1)[0]
      let lastClicked = -Infinity
      let timer = null
      let liked = false
      a.onclick = e => {
        e.preventDefault()
        if (Date.now() - lastClicked < 500) {
          if (liked) vote(id, 2)
          else vote(id, 3)
          liked = !liked
          a.classList.toggle('liked', liked)
          clearTimeout(timer)
        } else {
          lastClicked = +Date.now()
          timer = setTimeout(() => openUrl(url), 600)
        }
        return false
      }
    })
}

function openUrl(url) {
  console.log(url)
  if (!previewIframe) initPreviewIframe()
  previewIframe.onload = () => {
    if (previewIframe.contentWindow.location.href !== url) {
      location.href = previewIframe.contentWindow.location.href
      previewIframeDialog.classList.toggle('hidden', true)
      previewIframe.onload = null
    }
  }
  previewIframe.src = url
  previewIframeDialog.classList.toggle('hidden', false)
}

async function vote(id, score) {
  let body = new FormData()
  body.append('id', id)
  body.append('score', score)
  const rawResponse = await fetch('https://yande.re/post/vote.json', {
    method: 'POST',
    headers: {
      'X-CSRF-Token': document.querySelector('meta[name=csrf-token]').attributes
        .content.value
    },
    body
  })
  const content = await rawResponse.json()

  console.log(content)
}