Embedded Image Viewer

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

As of 2023-03-17. See the latest version.

  1. // ==UserScript==
  2. // @name Embedded Image Viewer
  3. // @namespace Violentmonkey Scripts
  4. // @match *://*.furaffinity.net/browse/*
  5. // @match *://*.furaffinity.net/gallery/*
  6. // @match *://*.furaffinity.net/search/*
  7. // @match *://*.furaffinity.net/favorites/*
  8. // @match *://*.furaffinity.net/controls/favorites/*
  9. // @match *://*.furaffinity.net/controls/submissions/*
  10. // @match *://*.furaffinity.net/msg/submissions/*
  11. // @exclude-match *://*.furaffinity.net/view/*
  12. // @grant none
  13. // @version 1.3
  14. // @author Midori Dragon
  15. // @description Embedds the clicked Image on the Current Site, so you can view it without loading the submission Page
  16. // @icon https://www.furaffinity.net/themes/beta/img/banners/fa_logo.png?v2
  17. // @homepageURL https://greatest.deepsurf.us/de/scripts/458971-embedded-image-viewer
  18. // @supportURL https://greatest.deepsurf.us/de/scripts/458971-embedded-image-viewer/feedback
  19. // @license MIT
  20. // ==/UserScript==
  21.  
  22. // jshint esversion: 8
  23.  
  24. //User Options:
  25. const loadingSpinSpeed = 100; //Sets the spinning speed of the loading animation in milliseconds
  26.  
  27.  
  28. let isShowing = false;
  29. let notClosingElemsArr = [];
  30.  
  31. document.addEventListener("click", function(event) {
  32. if (isShowing && !notClosingElemsArr.includes(event.target.id)) {
  33. event.preventDefault();
  34. document.getElementById("embeddedElem").remove();
  35. isShowing = false;
  36. }
  37. });
  38.  
  39. for (const figure of document.getElementsByTagName("figure")) {
  40. figure.addEventListener("click", function(event) {
  41. if (!event.ctrlKey && !event.target.id.includes("favbutton") && event.target.type != "checkbox") {
  42. if (event.target.href)
  43. return;
  44. else
  45. event.preventDefault();
  46. if (!isShowing)
  47. showImage(figure);
  48. }
  49. });
  50. }
  51.  
  52. async function showImage(figure) {
  53. const ddmenu = document.getElementById("ddmenu");
  54. const imageID = figure.id.substring(figure.id.indexOf("-") + 1);
  55. const favdoc = await getHTML("https://www.furaffinity.net/view/" + imageID);
  56.  
  57. await createElements(ddmenu, favdoc, imageID, figure);
  58.  
  59. isShowing = true;
  60. }
  61.  
  62. async function createElements(ddmenu, favdoc, imageID, figure) {
  63. const margin = 20;
  64.  
  65. let embeddedElem = document.createElement("div");
  66. embeddedElem.id = "embeddedElem";
  67. embeddedElem.style.position = "fixed";
  68. embeddedElem.style.zIndex = "999999";
  69. embeddedElem.style.width = window.innerWidth + "px";
  70. embeddedElem.style.height = window.innerHeight + "px";
  71. embeddedElem.style.background = "rgba(30,33,38,.65)";
  72.  
  73. let backgroundElem = document.createElement("div");
  74. backgroundElem.id = "embeddedBackgroundElem";
  75. notClosingElemsArr.push(backgroundElem.id);
  76. backgroundElem.style.position = "fixed";
  77. backgroundElem.style.display = "flex";
  78. backgroundElem.style.flexDirection = "column";
  79. backgroundElem.style.left = "50%";
  80. backgroundElem.style.transform = "translate(-50%, 0%)";
  81. backgroundElem.style.marginTop = margin + "px";
  82. backgroundElem.style.padding = margin + "px";
  83. backgroundElem.style.background = "rgba(30,33,38,.90)";
  84. backgroundElem.style.borderRadius = "10px";
  85.  
  86. let submissionContainer = document.createElement("a");
  87. submissionContainer.id = "embeddedSubmissionContainer";
  88. notClosingElemsArr.push(submissionContainer.id);
  89. submissionContainer.href = favdoc.querySelector('meta[property="og:url"]').content;
  90.  
  91. let submissionImg = favdoc.getElementById("submissionImg");
  92. submissionImg.id = "embeddedSubmissionImg";
  93. notClosingElemsArr.push(submissionImg.id);
  94. submissionImg.removeAttribute("data-fullview-src");
  95. submissionImg.removeAttribute("data-preview-src");
  96. submissionImg.style.maxWidth = "inherit";
  97. submissionImg.style.maxHeight = "inherit";
  98. submissionImg.style.maxWidth = window.innerWidth - margin * 2 + "px";
  99. submissionImg.style.maxHeight = window.innerHeight - ddmenu.clientHeight - 38 * 2 - margin * 2 - 100 + "px";
  100. submissionImg.style.borderRadius = "10px";
  101. submissionContainer.appendChild(submissionImg);
  102.  
  103. backgroundElem.appendChild(submissionContainer);
  104.  
  105. let buttonsContainer = document.createElement("div");
  106. buttonsContainer.id = "embeddedButtonsContainer";
  107. notClosingElemsArr.push(buttonsContainer.id);
  108. buttonsContainer.style.marginTop = "20px";
  109. buttonsContainer.style.marginLeft = "20px";
  110. buttonsContainer.style.marginRight = "20px";
  111.  
  112. let embeddedFavButton = document.createElement("a");
  113. embeddedFavButton.id = "embeddedFavButton";
  114. notClosingElemsArr.push(embeddedFavButton.id);
  115. embeddedFavButton.type = "button";
  116. embeddedFavButton.className = "button standard mobile-fix";
  117.  
  118. let favlink;
  119. //Fast Favoriter 2 integration <start>
  120. const favButton = figure.querySelector('[type="button"][class="button standard mobile-fix"]');
  121. if (favButton) {
  122. favlink = favButton.getAttribute("favlink");
  123. console.log(favlink);
  124. embeddedFavButton.textContent = favButton.textContent;
  125. } else { //Fast Favoriter 2 integration <end>
  126. favlink = await getfavlink(favdoc);
  127. if (favlink.includes("unfav"))
  128. embeddedFavButton.textContent = "-Fav";
  129. else
  130. embeddedFavButton.textContent = "+Fav";
  131. }
  132. embeddedFavButton.style.marginLeft = "4px";
  133. embeddedFavButton.style.marginRight = "4px";
  134. embeddedFavButton.onclick = function() {
  135. let erotation = rotateText(embeddedFavButton);
  136. console.log(favlink)
  137. favImage(figure, favlink, '', erotation);
  138. };
  139. buttonsContainer.appendChild(embeddedFavButton);
  140.  
  141. let downloadButton = document.createElement("a");
  142. downloadButton.id = "embeddedDownloadButton";
  143. notClosingElemsArr.push(downloadButton.id);
  144. downloadButton.type = "button";
  145. downloadButton.className = "button standard mobile-fix";
  146. downloadButton.textContent = "Download";
  147. downloadButton.style.marginLeft = "4px";
  148. downloadButton.style.marginRight = "4px";
  149. const downloadLink = await getDownloadLink(favdoc);
  150. downloadButton.href = downloadLink;
  151. downloadButton.download = downloadLink.substring(downloadLink.lastIndexOf("/") + 1);
  152. buttonsContainer.appendChild(downloadButton);
  153.  
  154. let closeButton = document.createElement("a");
  155. closeButton.id = "embeddedCloseButton";
  156. notClosingElemsArr.push(closeButton.id);
  157. closeButton.type = "button";
  158. closeButton.className = "button standard mobile-fix";
  159. closeButton.textContent = "Close";
  160. closeButton.style.marginLeft = "4px";
  161. closeButton.style.marginRight = "4px";
  162. closeButton.onclick = function() {
  163. ddmenu.removeChild(embeddedElem);
  164. isShowing = false;
  165. };
  166. buttonsContainer.appendChild(closeButton);
  167.  
  168. backgroundElem.appendChild(buttonsContainer);
  169.  
  170. embeddedElem.appendChild(backgroundElem);
  171.  
  172. ddmenu.appendChild(embeddedElem);
  173. }
  174.  
  175. async function favImage(figure, favlink, rotation, erotation) {
  176. if (!figure)
  177. return;
  178.  
  179. let footer = document.getElementById("footer");
  180. let iframe = document.createElement("iframe");
  181. iframe.id = "favIFrame";
  182. iframe.src = favlink;
  183. iframe.style.display = "none";
  184. iframe.addEventListener("load", async function() {
  185. let favdoc = iframe.contentDocument;
  186. footer.removeChild(iframe);
  187. let imageLink = figure.childNodes[0].childNodes[0].childNodes[0].href;
  188. favlink = await getfavlink(favdoc);
  189. if (!favlink) {
  190. checkFavLinkMissingReason(figure, favdoc);
  191. return;
  192. }
  193. changeFavButtonLink(favlink, figure, rotation, erotation);
  194. });
  195. footer.appendChild(iframe);
  196. }
  197.  
  198. async function checkFavLinkMissingReason(figure, favdoc) {
  199. favOnError(figure);
  200. let blocked = favdoc.getElementById("standardpage").querySelector('div[class="redirect-message"]');
  201. if (blocked && blocked.textContent.includes("blocked"))
  202. alert(blocked.textContent);
  203. }
  204.  
  205. async function favOnError(figure) {
  206. let embeddedFavButton = document.getElementById("embeddedFavButton");
  207. if (embeddedFavButton)
  208. embeddedFavButton.textContent = "x";
  209.  
  210. //Fast Favoriter 2 integration <start>
  211. let favButton = figure.querySelector('[type="button"][class="button standard mobile-fix"]');
  212. if (favButton)
  213. favButton.textContent = "x";
  214. //Fast Favoriter 2 integration <end>
  215. }
  216.  
  217. async function changeFavButtonLink(favlink, figure, rotation, erotation) {
  218. let embeddedFavButton = document.getElementById("embeddedFavButton");
  219. if (embeddedFavButton) {
  220. erotation();
  221. if (favlink.includes("unfav"))
  222. embeddedFavButton.textContent = "-Fav";
  223. else
  224. embeddedFavButton.textContent = "+Fav";
  225. embeddedFavButton.onclick = function() {
  226. erotation = rotateText(embeddedFavButton);
  227. favImage(figure, favlink, rotation, erotation);
  228. };
  229. }
  230.  
  231. //Fast Favoriter 2 integration <start>
  232. let favButton = figure.querySelector('[type="button"][class="button standard mobile-fix"]');
  233. if (favButton) {
  234. if (rotation)
  235. rotation();
  236. if (favlink.includes("unfav"))
  237. favButton.textContent = "-Fav";
  238. else
  239. favButton.textContent = "+Fav";
  240. favButton.onclick = function() {
  241. rotation = rotateText(favButton);
  242. favImage(figure, favlink, rotation, erotation);
  243. };
  244. }
  245. //Fast Favoriter 2 integration <end>
  246. }
  247.  
  248. function rotateText(element) {
  249. let isRotating = true;
  250. const characters = [ "◜", "◠", "◝", "◞", "◡", "◟" ];
  251. let index = 0;
  252.  
  253. function update() {
  254. if (!isRotating) return;
  255. element.textContent = characters[index % characters.length];
  256. index++;
  257. setTimeout(update, loadingSpinSpeed);
  258. }
  259. if (!isRotating) return;
  260. update();
  261.  
  262. return function stopRotation() {
  263. isRotating = false;
  264. };
  265. }
  266.  
  267. async function getfavlink(subdoc) {
  268. let buttons = subdoc.querySelectorAll('a[class="button standard mobile-fix"]');
  269. for (const button of buttons)
  270. if (button.textContent.includes("Fav") && button.textContent.length <= 4)
  271. return button.href;
  272. }
  273.  
  274. async function getDownloadLink(subdoc) {
  275. let buttons = subdoc.querySelectorAll('a[class="button standard mobile-fix"]');
  276. for (const button of buttons)
  277. if (button.textContent.includes("Download"))
  278. return button.href;
  279. }
  280.  
  281. async function getHTML(url) {
  282. try {
  283. const response = await fetch(url);
  284. const html = await response.text();
  285. const parser = new DOMParser();
  286. const doc = parser.parseFromString(html, "text/html");
  287. return doc;
  288. } catch (error) {
  289. console.error(error);
  290. }
  291. }