Greasy Fork is available in English.

Embedded Image Viewer

Embedds the clicked Image on the Current Site, so you can view it without loading the submission Page

Version vom 11.09.2023. Aktuellste Version

// ==UserScript==
// @name        Embedded Image Viewer
// @namespace   Violentmonkey Scripts
// @match       *://*.furaffinity.net/*
// @require 		https://gf.zukizuki.org/scripts/475041-furaffinity-custom-settings/code/Furaffinity-Custom-Settings.js
// @grant       none
// @version     1.5.0
// @author      Midori Dragon
// @description Embedds the clicked Image on the Current Site, so you can view it without loading the submission Page
// @icon        https://www.furaffinity.net/themes/beta/img/banners/fa_logo.png?v2
// @homepageURL https://gf.zukizuki.org/de/scripts/458971-embedded-image-viewer
// @supportURL  https://gf.zukizuki.org/de/scripts/458971-embedded-image-viewer/feedback
// @license     MIT
// ==/UserScript==

// jshint esversion: 8

const matchList = ['net/browse', 'net/gallery', 'net/search', 'net/favorites', 'net/scraps', 'net/controls/favorites', 'net/controls/submissions', 'net/msg/submissions', 'd.furaffinity.net' ];

const isDFuraffinity = window.location.toString().includes("d.furaffinity.net");
const isDownloadImage = window.location.toString().includes("?eidownload");

if (isDFuraffinity) {
	if (isDownloadImage)
		downloadImage();
	return;
}

CustomSettings.Name = "Extension Settings";
CustomSettings.Provider = "Midori's Script Settings";
CustomSettings.HeaderName = `${GM_info.script.name} Settings`;
const preventInstantDownloadSetting = new Setting("Prevent Instant Download", "Sets wether to instantly download the Image when the download Button is pressed.", SettingTypes.Boolean, "Prevent Instant Download", false);
const loadingSpinSpeedSetting = new Setting("Fav Loading Animation", "Sets the spinning speed of the loading animation in milliseconds.", SettingTypes.Number, "", 100);
CustomSettings.loadSettings();

let color = "color: blue";
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)
    color = "color: aqua";
if (window.location.toString().includes("?extension")) {
	console.info(`%cSettings: ${GM_info.script.name} v${GM_info.script.version}`, color);
	return;
}

if (!matchList.some(x => window.location.toString().includes(x)))
  return;

console.info(`%cRunning: ${GM_info.script.name} v${GM_info.script.version} ${CustomSettings.toString()}`, color);

let isShowing = false;
let notClosingElemsArr = [];

document.addEventListener("click", function(event) {
  if (isShowing && !notClosingElemsArr.includes(event.target.id)) {
    event.preventDefault();
    document.getElementById("embeddedElem").remove();
    isShowing = false;
  }
});

addEmbedded();

window.updateEmbedded = function() { addEmbedded(); };

async function addEmbedded() {
  for (const figure of document.querySelectorAll('figure:not([embedded])')) {
    figure.setAttribute('embedded', true);
    figure.addEventListener("click", function(event) {
      if (!event.ctrlKey && !event.target.id.includes("favbutton") && event.target.type != "checkbox") {
        if (event.target.href)
          return;
        else
          event.preventDefault();
        if (!isShowing)
          showImage(figure);
      }
    });
  }
}

async function showImage(figure) {
  const ddmenu = document.getElementById("ddmenu");
  const imageID = figure.id.substring(figure.id.indexOf("-") + 1);
  const favdoc = await getHTML("https://www.furaffinity.net/view/" + imageID);

  await createElements(ddmenu, favdoc, imageID, figure);

  isShowing = true;
}

async function createElements(ddmenu, favdoc, imageID, figure) {
  const margin = 20;

  let embeddedElem = document.createElement("div");
  embeddedElem.id = "embeddedElem";
  embeddedElem.style.position = "fixed";
  embeddedElem.style.zIndex = "999999";
  embeddedElem.style.width = window.innerWidth + "px";
  embeddedElem.style.height = window.innerHeight + "px";
  embeddedElem.style.background = "rgba(30,33,38,.65)";

  let backgroundElem = document.createElement("div");
  backgroundElem.id = "embeddedBackgroundElem";
  notClosingElemsArr.push(backgroundElem.id);
  backgroundElem.style.position = "fixed";
  backgroundElem.style.display = "flex";
  backgroundElem.style.flexDirection = "column";
  backgroundElem.style.left = "50%";
  backgroundElem.style.transform = "translate(-50%, 0%)";
  backgroundElem.style.marginTop = margin + "px";
  backgroundElem.style.padding = margin + "px";
  backgroundElem.style.background = "rgba(30,33,38,.90)";
  backgroundElem.style.borderRadius = "10px";

  let submissionContainer = document.createElement("a");
  submissionContainer.id = "embeddedSubmissionContainer";
  notClosingElemsArr.push(submissionContainer.id);
  submissionContainer.href = favdoc.querySelector('meta[property="og:url"]').content;

  let submissionImg = favdoc.getElementById("submissionImg");
  submissionImg.id = "embeddedSubmissionImg";
  notClosingElemsArr.push(submissionImg.id);
  submissionImg.removeAttribute("data-fullview-src");
  submissionImg.removeAttribute("data-preview-src");
  submissionImg.style.maxWidth = "inherit";
  submissionImg.style.maxHeight = "inherit";
  submissionImg.style.maxWidth = window.innerWidth - margin * 2 + "px";
  submissionImg.style.maxHeight = window.innerHeight - ddmenu.clientHeight - 38 * 2 - margin * 2 - 100 + "px";
  submissionImg.style.borderRadius = "10px";
  submissionContainer.appendChild(submissionImg);

  backgroundElem.appendChild(submissionContainer);

  let buttonsContainer = document.createElement("div");
  buttonsContainer.id = "embeddedButtonsContainer";
  notClosingElemsArr.push(buttonsContainer.id);
  buttonsContainer.style.marginTop = "20px";
  buttonsContainer.style.marginLeft = "20px";
  buttonsContainer.style.marginRight = "20px";

  let embeddedFavButton = document.createElement("a");
  embeddedFavButton.id = "embeddedFavButton";
  notClosingElemsArr.push(embeddedFavButton.id);
  embeddedFavButton.type = "button";
  embeddedFavButton.className = "button standard mobile-fix";

  let favlink;
  //Fast Favoriter 2 integration <start>
  const favButton = figure.querySelector('[type="button"][class="button standard mobile-fix"]');
  if (favButton) {
    favlink = favButton.getAttribute("favlink");
    console.log(favlink);
    embeddedFavButton.textContent = favButton.textContent;
  } else { //Fast Favoriter 2 integration <end>
    favlink = await getfavlink(favdoc);
    if (favlink.includes("unfav"))
      embeddedFavButton.textContent = "-Fav";
    else
      embeddedFavButton.textContent = "+Fav";
  }
  embeddedFavButton.style.marginLeft = "4px";
  embeddedFavButton.style.marginRight = "4px";
  embeddedFavButton.onclick = function() {
    let erotation = rotateText(embeddedFavButton);
    console.log(favlink)
    favImage(figure, favlink, '', erotation);
  };
  buttonsContainer.appendChild(embeddedFavButton);

  let downloadButton = document.createElement("a");
  downloadButton.id = "embeddedDownloadButton";
  notClosingElemsArr.push(downloadButton.id);
  downloadButton.type = "button";
  downloadButton.className = "button standard mobile-fix";
  downloadButton.textContent = "Download";
  downloadButton.style.marginLeft = "4px";
  downloadButton.style.marginRight = "4px";
  const downloadLink = await getDownloadLink(favdoc);
	if (preventInstantDownloadSetting.value)
  	downloadButton.href = downloadLink;
	else
		downloadButton.href = downloadLink + "?eidownload";
	downloadButton.target = "_blank";
  downloadButton.download = downloadLink.substring(downloadLink.lastIndexOf("/") + 1);
  buttonsContainer.appendChild(downloadButton);

  let closeButton = document.createElement("a");
  closeButton.id = "embeddedCloseButton";
  notClosingElemsArr.push(closeButton.id);
  closeButton.type = "button";
  closeButton.className = "button standard mobile-fix";
  closeButton.textContent = "Close";
  closeButton.style.marginLeft = "4px";
  closeButton.style.marginRight = "4px";
  closeButton.onclick = function() {
    ddmenu.removeChild(embeddedElem);
    isShowing = false;
  };
  buttonsContainer.appendChild(closeButton);

  backgroundElem.appendChild(buttonsContainer);

  embeddedElem.appendChild(backgroundElem);

  ddmenu.appendChild(embeddedElem);
}

async function favImage(figure, favlink, rotation, erotation) {
  if (!figure)
    return;

  let footer = document.getElementById("footer");
  let iframe = document.createElement("iframe");
  iframe.id = "favIFrame";
  iframe.src = favlink;
  iframe.style.display = "none";
  iframe.addEventListener("load", async function() {
    let favdoc = iframe.contentDocument;
    footer.removeChild(iframe);
    let imageLink = figure.childNodes[0].childNodes[0].childNodes[0].href;
    favlink = await getfavlink(favdoc);
    if (!favlink) {
      checkFavLinkMissingReason(figure, favdoc);
      return;
    }
    changeFavButtonLink(favlink, figure, rotation, erotation);
  });
  footer.appendChild(iframe);
}

async function checkFavLinkMissingReason(figure, favdoc) {
  favOnError(figure);
  let blocked = favdoc.getElementById("standardpage").querySelector('div[class="redirect-message"]');
  if (blocked && blocked.textContent.includes("blocked"))
    alert(blocked.textContent);
}

async function favOnError(figure) {
  let embeddedFavButton = document.getElementById("embeddedFavButton");
  if (embeddedFavButton)
    embeddedFavButton.textContent = "x";

  //Fast Favoriter 2 integration <start>
  let favButton = figure.querySelector('[type="button"][class="button standard mobile-fix"]');
  if (favButton)
    favButton.textContent = "x";
  //Fast Favoriter 2 integration <end>
}

async function changeFavButtonLink(favlink, figure, rotation, erotation) {
  let embeddedFavButton = document.getElementById("embeddedFavButton");
  if (embeddedFavButton) {
    erotation();
    if (favlink.includes("unfav"))
      embeddedFavButton.textContent = "-Fav";
    else
      embeddedFavButton.textContent = "+Fav";
    embeddedFavButton.onclick = function() {
      erotation = rotateText(embeddedFavButton);
      favImage(figure, favlink, rotation, erotation);
    };
  }

  //Fast Favoriter 2 integration <start>
  let favButton = figure.querySelector('[type="button"][class="button standard mobile-fix"]');
  if (favButton) {
    if (rotation)
      rotation();
    if (favlink.includes("unfav"))
      favButton.textContent = "-Fav";
    else
      favButton.textContent = "+Fav";
    favButton.onclick = function() {
      rotation = rotateText(favButton);
      favImage(figure, favlink, rotation, erotation);
    };
  }
  //Fast Favoriter 2 integration <end>
}

function rotateText(element) {
  let isRotating = true;
  const characters = [ "◜", "◠", "◝", "◞", "◡", "◟" ];
  let index = 0;

  function update() {
    if (!isRotating) return;
    element.textContent = characters[index % characters.length];
    index++;
    setTimeout(update, loadingSpinSpeedSetting.value);
  }
  if (!isRotating) return;
  update();

  return function stopRotation() {
    isRotating = false;
  };
}

function downloadImage() {
	console.log("Embedded Image Viewer downloading Image...");
	let url = window.location.toString();
	if (url.includes("?")) {
		const parts = url.split('?');
    url = parts[0];
	}
	const download = document.createElement('a');
	download.href = url;
	download.download = url.substring(url.lastIndexOf("/") + 1);
	download.style.display = 'none';
	document.body.appendChild(download);
	download.click();
	document.body.removeChild(download);

	window.close();
}

async function getfavlink(subdoc) {
  let buttons = subdoc.querySelectorAll('a[class="button standard mobile-fix"]');
  for (const button of buttons)
    if (button.textContent.includes("Fav") && button.textContent.length <= 4)
      return button.href;
}

async function getDownloadLink(subdoc) {
  let buttons = subdoc.querySelectorAll('a[class="button standard mobile-fix"]');
  for (const button of buttons)
    if (button.textContent.includes("Download"))
      return button.href;
}

async function getHTML(url) {
  try {
    const response = await fetch(url);
    const html = await response.text();
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, "text/html");
    return doc;
  } catch (error) {
    console.error(error);
  }
}