yande.re refine

Refining yande.re

2020-05-09 या दिनांकाला. सर्वात नवीन आवृत्ती पाहा.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

You will need to install an extension such as Tampermonkey to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name           yande.re refine
// @namespace      https://greasyfork.org/scripts/397612-yande-re-refine
// @description    Refining yande.re
// @include        *://behoimi.org/*
// @include        *://www.behoimi.org/*
// @include        *://*.donmai.us/*
// @include        *://konachan.tld/*
// @include        *://yande.re/*
// @include        *://chan.sankakucomplex.com/*
// @version        2020.04.10.a
// @grant          none
// ==/UserScript==

const STORAGE_KEY = "yandere-refine-liked";
const SUGGEST_WIDTH = 300;
//Minimum amount of window left to scroll, maintained by loading more pages.
const 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.
const timeToFailure = 15000;

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

var nextPage, mainTable, mainParent, timeout, iframe;
let previewIframe, previewImage, previewImageDiv, previewDialog;
let imagesList = [];
let currentImage = 0;
let pending = ref(false, v =>
  document.getElementById("loader").classList.toggle("hidden", !v)
);
let likedList = readLikeList();
let viewingFavorites = isViewingFavorites();
let db;

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;

  injectStyle();
  initDOM();
  addImages(getImages());

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

  //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:
  mainParent = mainTable.parentNode;
  pending.value = 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
  );

  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) 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 getNextPage(doc = document) {
  return (doc.querySelector("a.next_page") || {}).href;
}

function testScrollPosition() {
  if (!nextPage) return;

  //Take the max of the two heights for browser compatibility
  if (
    !pending.value &&
    window.pageYOffset + window.innerHeight + scrollBuffer >
      Math.max(
        document.documentElement.scrollHeight,
        document.documentElement.offsetHeight
      )
  ) {
    console.log("loading " + nextPage);
    pending.value = true;
    timeout = setTimeout(function() {
      pending.value = 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.value = false;
    }, 1000);
    return;
  }

  let images = getImages(iframe.contentDocument);
  addImages(images);

  if (!images.length) nextPage = null;
  else {
    nextPage = getNextPage(iframe.contentDocument);
  }

  if (nextPage) {
    history.pushState({}, iframe.contentDocument, nextPage);
  } else {
    // TODO: end of pages
    console.log("End of pages");
  }

  pending.value = false;
  testScrollPosition();
}

function injectGlobalStyle() {
  const s = document.createElement("style");
  s.innerHTML = `
body { padding: 0; }
#header { margin: 0 !important; text-align: center; }
#header ul { float: none !important; display: inline-block;}
#content > div:first-child > div.sidebar { position: fixed; left: 0; top: 0; bottom: 0; overflow: auto !important; z-index: 2; width: 200px !important; transform: translate(-200px, calc(-100vh + 30px)); border-bottom-right-radius: 30px; background: #171717dd; transition: all .2s ease-out; float: none !important; padding: 15px; }
#content > div:first-child > div.sidebar:hover { transform: translateX(0); }
div.content { width: 100vw; text-align: center; float: none }
div.footer { clear: both !important; }
div#paginator a { border: none; }
#comments { max-width: unset !important; width: unset !important; padding: 20px; }
.avatar { border-radius: 1000px; }
form textarea { color: white; background: inherit; padding: 10px 5px; }
.comment .content { text-align: left; }
`;
  document.body.appendChild(s);
}

function injectStyle() {
  const s = document.createElement("style");
  s.innerHTML = `
#gallery .row { width: 100vw; white-space: nowrap; height: var(--image-height); --image-height: 300px; }
#gallery .row .thumb { position: relative; display: inline-block; transition: .2s ease-out; overflow: hidden; }
#gallery .row .thumb.liked::after { position: absolute; top: 3px; right: 3px; content: url('https://api.iconify.design/mdi:cards-heart.svg?color=%23f37e92&height=20'); vertical-align: -0.125em; }
#gallery .row .thumb:first-child { transform-origin: left; }
#gallery .row .thumb:last-child { transform-origin: right; }
#gallery .row .thumb img { height: var(--image-height); }
#gallery .row:hover { z-index: 1; }
#gallery .row .thumb:hover { transform: scale(1.3); z-index: 1; opacity: 1; box-shadow: 8px 8px 100px 10px rgba(0, 0, 0, 0.8); border-radius: 5px;  }

#loader { padding: 10px; text-align: center; }

.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; }
.preivew-dialog .image-host { position: fixed; top: 0; left: 0; height: 100vh; width: 100vw; overflow: auto; text-align: center; }
.preivew-dialog .image-host img { margin: auto; }
.preivew-dialog .image-host img.loading { filter: blur(3px); height: 100vh; }
.preivew-dialog .image-host.full { overflow: hidden }
.preivew-dialog .image-host.full img { max-width: 100vw; max-height: 100vh; }
`;
  document.body.appendChild(s);
}

function initPreviewIframe() {
  previewDialog = document.createElement("div");
  previewDialog.addClassName("preivew-dialog hidden");
  previewDialog.onclick = e => {
    if (e.target === previewDialog)
      previewDialog.classList.toggle("hidden", true);
  };
  window.onkeydown = e => {
    if (!previewDialog.classList.contains("hidden")) {
      if (e.key === "ArrowLeft") {
        currentImage = Math.max(0, currentImage - 1);
        openImage(currentImage);
        e.preventDefault();
      }
      if (e.key === "ArrowRight") {
        currentImage = Math.min(imagesList.length - 1, currentImage + 1);
        openImage(currentImage);
        e.preventDefault();
      }
      if (e.key === "Escape") {
        previewDialog.classList.toggle("hidden", true);
        e.preventDefault();
      }
      if (e.key === "Tab") {
        openImage(currentImage, "page");
        e.preventDefault();
      }
      if (e.code === "Space") {
        previewImageDiv.classList.toggle("full");
        e.preventDefault();
      }
      if (e.code === "KeyL") {
        like(currentImage, 3);
        e.preventDefault();
      }
      if (e.code === "KeyU") {
        unlike(currentImage, 2);
        e.preventDefault();
      }
    }
  };

  previewIframe = document.createElement("iframe");
  previewImageDiv = document.createElement("div");
  previewImageDiv.className = "image-host full";
  previewImage = document.createElement("img");

  previewImageDiv.onclick = e => {
    previewDialog.classList.toggle("hidden", true);
  };

  previewDialog.appendChild(previewIframe);
  previewImageDiv.appendChild(previewImage);
  previewDialog.appendChild(previewImageDiv);
  document.body.appendChild(previewDialog);
}

function ref(v, handler) {
  let value = v;
  return new Proxy(
    {},
    {
      get(obj, prop) {
        return value;
      },
      set(obj, prop, v) {
        if (value !== v) {
          value = v;
          handler(value);
        }
      }
    }
  );
}

function getImages(doc = document) {
  const result = Array.from(
    doc.querySelectorAll("ul#post-list-posts > li")
  ).map(li => {
    const page = (li.querySelector("a.thumb") || {}).href;
    const thumb = (li.querySelector("a.thumb img") || {}).src;
    const large = (li.querySelector("a.largeimg") || {}).href || (li.querySelector("a.smallimg") || {}).href;
    const id = page.split("/").slice(-1)[0];
    const resText = (li.querySelector(".directlink-res") || {}).innerText;
    let res = undefined;
    if (resText && resText.includes("x")) {
      let [height, width] = resText.split(" x ").map(i => +i);
      res = { height, width, radio: width / height };
    }
    if (viewingFavorites) setLiked(id, true);
    let liked = isLiked(id);

    return { page, thumb, large, id, res, liked };
  });
  doc.getElementById("post-list-posts").remove();
  return result;
}

function initDOM() {
  const list = document.getElementById("post-list");
  const gallery = document.createElement("div");
  gallery.id = "gallery";
  list.appendChild(gallery);
  const loader = document.createElement("div");
  loader.id = "loader";
  loader.innerText = "Loading...";
  list.appendChild(loader);
}

function addImages(images) {
  const gallery = document.getElementById("gallery");
  const RADIO = Math.round(window.innerWidth / SUGGEST_WIDTH);
  images.forEach((info, i) => {
    let idx = imagesList.length + i;
    let row = gallery.querySelector(".row:last-child:not(.full)");
    if (!row) {
      row = document.createElement("div");
      row.className = "row";
      gallery.appendChild(row);
    }
    row.dataset.width = +(row.dataset.width || 0) + 1 / info.res.radio;

    if (+row.dataset.width >= RADIO) {
      row.classList.toggle("full", true);
      row.style = `--image-height: calc(100vw / ${row.dataset.width})`;
    }

    const thumb = document.createElement("div");
    thumb.className = "thumb";
    row.appendChild(thumb);

    const img = document.createElement("img");
    img.src = info.thumb;
    thumb.appendChild(img);
    info.dom = thumb;
    thumb.classList.toggle("liked", info.liked);

    let lastClicked = -Infinity;
    let timer = null;
    img.onclick = e => {
      e.preventDefault();
      // double click
      if (Date.now() - lastClicked < 300) {
        if (info.liked) unlike(idx);
        else like(idx);

        clearTimeout(timer);
        // click
      } else {
        lastClicked = +Date.now();
        timer = setTimeout(() => openImage(idx), 400);
      }
      return false;
    };
  });
  imagesList.push(...images);
}

function openImage(idx, type = "image") {
  currentImage = idx;
  const img = imagesList[idx];
  const { page, large, id, thumb } = img;
  if (!previewIframe) initPreviewIframe();

  if (!large) type = "page";

  if (type === "image") {
    previewImage.classList.toggle("loading", true);
    previewImage.src = thumb;
    previewImage.onload = () => {
      previewImage.src = large;
      previewImage.onload = () => {
        previewImage.classList.toggle("loading", false);
        previewImage.onload = null;
      };
    };
    previewIframe.classList.toggle("hidden", true);
    previewImageDiv.classList.toggle("hidden", false);
  } else {
    previewIframe.onload = () => {
      if (
        previewIframe.contentWindow.location.href !== page &&
        !previewIframe.contentWindow.location.pathname.startsWith("/post/show/")
      ) {
        location.href = previewIframe.contentWindow.location.href;
        previewDialog.classList.toggle("hidden", true);
        previewIframe.onload = null;
      }
    };
    previewIframe.src = page;
    previewIframe.classList.toggle("hidden", false);
    previewImageDiv.classList.toggle("hidden", true);
  }
  previewDialog.classList.toggle("hidden", false);
}

function getFavoritesLike() {
  return document.querySelector(".user .submenu li:nth-child(3) a").href;
}

function isViewingFavorites() {
  let fav = getFavoritesLike();
  if (!fav) return false;

  let a = (new URL(fav).searchParams.get("tags") || "")
    .toLowerCase()
    .split(" ")
    .sort();
  let b = (new URL(location.href).searchParams.get("tags") || "")
    .toLowerCase()
    .split(" ")
    .sort();

  return a[0] == b[0] && a[1] == b[1];
}

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

function readLikeList() {
  return Object.fromEntries(
    (localStorage.getItem(STORAGE_KEY) || "").split(",").map(i => [i, true])
  );
}

function isLiked(id) {
  return !!likedList[id];
}

function setLiked(id, v) {
  likedList[id] = v;
  localStorage.setItem(
    STORAGE_KEY,
    Object.entries(likedList)
      .map(([i, v]) => (v ? i : null))
      .filter(i => i)
      .join(",")
  );
}

function like(idx) {
  let image = imagesList[idx];
  vote(image.id, 3);
  image.liked = true;
  setLiked(image.id, true);
  image.dom.classList.toggle("liked", image.liked);
}

function unlike(idx) {
  let image = imagesList[idx];
  vote(image.id, 2);
  image.liked = false;
  setLiked(image.id, false);
  image.dom.classList.toggle("liked", image.liked);
}