FA Embedded Image Viewer

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

Fra 27.01.2024. Se den seneste versjonen.

  1. // ==UserScript==
  2. // @name FA Embedded Image Viewer
  3. // @namespace Violentmonkey Scripts
  4. // @match *://*.furaffinity.net/*
  5. // @require https://update.greatest.deepsurf.us/scripts/475041/1267274/Furaffinity-Custom-Settings.js
  6. // @require https://update.greatest.deepsurf.us/scripts/483952/1306729/Furaffinity-Request-Helper.js
  7. // @require https://update.greatest.deepsurf.us/scripts/485153/1316289/Furaffinity-Loading-Animations.js
  8. // @require https://update.greatest.deepsurf.us/scripts/476762/1318215/Furaffinity-Custom-Pages.js
  9. // @require https://update.greatest.deepsurf.us/scripts/485827/1318253/Furaffinity-Match-List.js
  10. // @grant none
  11. // @version 2.0.9
  12. // @author Midori Dragon
  13. // @description Embedds the clicked Image on the Current Site, so you can view it without loading the submission Page
  14. // @icon https://www.furaffinity.net/themes/beta/img/banners/fa_logo.png?v2
  15. // @homepageURL https://greatest.deepsurf.us/de/scripts/458971-embedded-image-viewer
  16. // @supportURL https://greatest.deepsurf.us/de/scripts/458971-embedded-image-viewer/feedback
  17. // @license MIT
  18. // ==/UserScript==
  19.  
  20. // jshint esversion: 8
  21.  
  22. CustomSettings.name = "Extension Settings";
  23. CustomSettings.provider = "Midori's Script Settings";
  24. CustomSettings.headerName = `${GM_info.script.name} Settings`;
  25. const preventInstantDownloadSetting = CustomSettings.newSetting("Prevent Instant Download", "Sets wether to instantly download the Image when the download Button is pressed.", SettingTypes.Boolean, "Prevent Instant Download", false);
  26. const loadingSpinSpeedFavSetting = CustomSettings.newSetting("Fav Loading Animation", "Sets the duration that the loading animation, for faving a submission, takes for a full rotation in milliseconds.", SettingTypes.Number, "", 600);
  27. const loadingSpinSpeedSetting = CustomSettings.newSetting("Embedded Loading Animation", "Sets the duration that the loading animation of the Embedded element to load takes for a full rotation in milliseconds.", SettingTypes.Number, "", 1000);
  28. CustomSettings.loadSettings();
  29.  
  30. const matchList = new MatchList(CustomSettings);
  31. matchList.matches = ['net/browse', 'net/gallery', 'net/search', 'net/favorites', 'net/scraps', 'net/controls/favorites', 'net/controls/submissions', 'net/msg/submissions', 'd.furaffinity.net'];
  32. matchList.runInIFrame = true;
  33. if (!matchList.hasMatch()) {
  34. console.log("no run");
  35. return;
  36. }
  37.  
  38. const page = new CustomPage("d.furaffinity.net", "eidownload");
  39. page.onopen = (data) => {
  40. downloadImage();
  41. return;
  42. };
  43.  
  44. const requestHelper = new FARequestHelper(2);
  45.  
  46. class EmbeddedImage {
  47. constructor(figure) {
  48. this.embeddedElem;
  49. this.backgroundElem;
  50. this.submissionContainer;
  51. this.submissionImg;
  52. this.buttonsContainer;
  53. this.favButton;
  54. this.downloadButton;
  55. this.closeButton;
  56.  
  57. this._onRemoveAction;
  58.  
  59. this.createStyle();
  60. this.createElements(figure);
  61.  
  62. this.loadingSpinner = new LoadingSpinner(this.submissionContainer);
  63. this.loadingSpinner.delay = loadingSpinSpeedSetting.value;
  64. this.loadingSpinner.spinnerThickness = 6;
  65. this.loadingSpinner.visible = true;
  66. this.fillSubDocInfos(figure);
  67. }
  68.  
  69. createStyle() {
  70. if (document.getElementById("embeddedStyle")) return;
  71. const style = document.createElement("style");
  72. style.id = "embeddedStyle";
  73. style.type = "text/css";
  74. style.innerHTML = `
  75. #embeddedElem {
  76. position: fixed;
  77. width: 100vw;
  78. height: 100vh;
  79. max-width: 1850px;
  80. z-index: 999999;
  81. background: rgba(30,33,38,.65);
  82. }
  83. #embeddedBackgroundElem {
  84. position: fixed;
  85. display: flex;
  86. flex-direction: column;
  87. left: 50%;
  88. transform: translate(-50%, 0%);
  89. margin-top: 20px;
  90. padding: 20px;
  91. background: rgba(30,33,38,.90);
  92. border-radius: 10px;
  93. }
  94. #embeddedSubmissionImg {
  95. max-width: inherit;
  96. max-height: inherit;
  97. border-radius: 10px;
  98. }
  99. #embeddedButtonsContainer {
  100. margin-top: 20px;
  101. margin-bottom: 20px;
  102. margin-left: 20px;
  103. }
  104. .embeddedButton {
  105. margin-left: 4px;
  106. margin-right: 4px;
  107. }
  108. `;
  109. document.head.appendChild(style);
  110. }
  111.  
  112. onRemove(action) {
  113. this._onRemoveAction = action;
  114. }
  115.  
  116. remove() {
  117. this.embeddedElem.parentNode.removeChild(this.embeddedElem);
  118. if (this._onRemoveAction)
  119. this._onRemoveAction();
  120. }
  121.  
  122. createElements(figure) {
  123. this.embeddedElem = document.createElement("div");
  124. this.embeddedElem.id = "embeddedElem";
  125. this.embeddedElem.onclick = (event) => {
  126. if (event.target == this.embeddedElem)
  127. this.remove();
  128. };
  129.  
  130. this.backgroundElem = document.createElement("div");
  131. this.backgroundElem.id = "embeddedBackgroundElem";
  132. notClosingElemsArr.push(this.backgroundElem.id);
  133.  
  134. this.submissionContainer = document.createElement("a");
  135. this.submissionContainer.id = "embeddedSubmissionContainer";
  136. notClosingElemsArr.push(this.submissionContainer.id);
  137.  
  138. this.backgroundElem.appendChild(this.submissionContainer);
  139.  
  140. this.buttonsContainer = document.createElement("div");
  141. this.buttonsContainer.id = "embeddedButtonsContainer";
  142. notClosingElemsArr.push(this.buttonsContainer.id);
  143.  
  144. this.favButton = document.createElement("a");
  145. this.favButton.id = "embeddedFavButton";
  146. notClosingElemsArr.push(this.favButton.id);
  147. this.favButton.type = "button";
  148. this.favButton.className = "embeddedButton button standard mobile-fix";
  149. this.favButton.textContent = "⠀⠀";
  150. this.buttonsContainer.appendChild(this.favButton);
  151.  
  152. this.downloadButton = document.createElement("a");
  153. this.downloadButton.id = "embeddedDownloadButton";
  154. notClosingElemsArr.push(this.downloadButton.id);
  155. this.downloadButton.type = "button";
  156. this.downloadButton.className = "embeddedButton button standard mobile-fix";
  157. this.downloadButton.textContent = "Download";
  158. // this.downloadButton.target = "_blank";
  159. this.buttonsContainer.appendChild(this.downloadButton);
  160.  
  161. this.openGalleryButton = document.createElement("a");
  162. this.openGalleryButton.id = "embeddedOpenGalleryButton";
  163. notClosingElemsArr.push(this.openGalleryButton.id);
  164. this.openGalleryButton.type = "button";
  165. this.openGalleryButton.className = "embeddedButton button standard mobile-fix";
  166. this.openGalleryButton.textContent = "Open Gallery";
  167. const links = figure.querySelector("figcaption").querySelectorAll("a[href][title]");
  168. const galleryLink = links[links.length - 1].toString().replace("user", "gallery");
  169. this.openGalleryButton.href = galleryLink;
  170. this.openGalleryButton.target = "_blank";
  171. this.buttonsContainer.appendChild(this.openGalleryButton);
  172.  
  173. this.closeButton = document.createElement("a");
  174. this.closeButton.id = "embeddedCloseButton";
  175. notClosingElemsArr.push(this.closeButton.id);
  176. this.closeButton.type = "button";
  177. this.closeButton.className = "embeddedButton button standard mobile-fix";
  178. this.closeButton.textContent = "Close";
  179. this.closeButton.onclick = () => this.remove();
  180. this.buttonsContainer.appendChild(this.closeButton);
  181.  
  182. this.backgroundElem.appendChild(this.buttonsContainer);
  183.  
  184. this.embeddedElem.appendChild(this.backgroundElem);
  185.  
  186. const ddmenu = document.getElementById("ddmenu");
  187. ddmenu.appendChild(this.embeddedElem);
  188. }
  189.  
  190. async fillSubDocInfos(figure) {
  191. const sid = figure.id.split("-")[1];
  192. const ddmenu = document.getElementById("ddmenu");
  193. const doc = await requestHelper.SubmissionRequests.getSubmissionPage(sid);
  194. if (this.loadingSpinner)
  195. this.loadingSpinner.visible = false;
  196. if (doc) {
  197. this.submissionImg = doc.getElementById("submissionImg");
  198. this.submissionImg.style.maxWidth = window.innerWidth - 20 * 2 + "px";
  199. this.submissionImg.style.maxHeight = window.innerHeight - ddmenu.clientHeight - 38 * 2 - 20 * 2 - 100 + "px";
  200. this.submissionContainer.appendChild(this.submissionImg);
  201. this.submissionContainer.href = doc.querySelector('meta[property="og:url"]').content;
  202.  
  203. const result = getFavKey(doc);
  204. this.favButton.textContent = result.isFav ? "+Fav" : "-Fav";
  205. this.favButton.setAttribute("isFav", result.isFav);
  206. this.favButton.setAttribute("key", result.favKey);
  207. this.favButton.onclick = () => this.doFavRequest(sid);
  208.  
  209. this.downloadButton.onclick = () => {
  210. const loadingTextSpinner = new LoadingTextSpinner(this.downloadButton);
  211. loadingTextSpinner.delay = loadingSpinSpeedFavSetting.value;
  212. loadingTextSpinner.visible = true;
  213. const iframe = document.createElement("iframe");
  214. iframe.style.display = "none";
  215. iframe.src = this.submissionImg.src + "?eidownload";
  216. iframe.onload = () => {
  217. setTimeout(() => iframe.parentNode.removeChild(iframe), 100);
  218. loadingTextSpinner.visible = false;
  219. };
  220. document.body.appendChild(iframe);
  221. };
  222. }
  223. }
  224.  
  225. async doFavRequest(sid) {
  226. const loadingTextSpinner = new LoadingTextSpinner(this.favButton);
  227. loadingTextSpinner.delay = loadingSpinSpeedFavSetting.value;
  228. loadingTextSpinner.visible = true;
  229. let favKey = this.favButton.getAttribute("key");
  230. let isFav = this.favButton.getAttribute("isFav");
  231. if (isFav == "true") {
  232. favKey = await requestHelper.SubmissionRequests.favSubmission(sid, favKey);
  233. loadingTextSpinner.visible = false;
  234. if (favKey) {
  235. this.favButton.setAttribute("key", favKey);
  236. isFav = false;
  237. this.favButton.setAttribute("isFav", isFav);
  238. this.favButton.textContent = "-Fav";
  239. } else {
  240. this.favButton.textContent = "x";
  241. setTimeout(() => this.favButton.textContent = "+Fav", 1000);
  242. }
  243. } else {
  244. favKey = await requestHelper.SubmissionRequests.unfavSubmission(sid, favKey);
  245. loadingTextSpinner.visible = false;
  246. if (favKey) {
  247. this.favButton.setAttribute("key", favKey);
  248. isFav = true;
  249. this.favButton.setAttribute("isFav", isFav);
  250. this.favButton.textContent = "+Fav";
  251. } else {
  252. this.favButton.textContent = "x";
  253. setTimeout(() => this.favButton.textContent = "-Fav", 1000);
  254. }
  255. }
  256. }
  257. }
  258.  
  259. function getFavKey(doc) {
  260. const columnPage = doc.getElementById("columnpage");
  261. const navbar = columnPage.querySelector('div[class*="favorite-nav"');
  262. const buttons = navbar.querySelectorAll('a[class*="button"][href]');
  263. let favButton;
  264. for (const button of buttons) {
  265. if (button.textContent.toLowerCase().includes("fav"))
  266. favButton = button;
  267. }
  268.  
  269. if (favButton) {
  270. const favKey = favButton.href.split("?key=")[1];
  271. const isFav = !favButton.href.toLowerCase().includes("unfav");
  272. return { favKey, isFav };
  273. }
  274. }
  275.  
  276. let isShowing = false;
  277. let notClosingElemsArr = [];
  278. let embeddedImage;
  279.  
  280. addEmbedded();
  281. window.updateEmbedded = addEmbedded;
  282.  
  283. document.addEventListener("click", (event) => {
  284. if (event.target.parentNode instanceof HTMLDocument && embeddedImage)
  285. embeddedImage.remove();
  286. })
  287.  
  288. async function addEmbedded() {
  289. for (const figure of document.querySelectorAll('figure:not([embedded])')) {
  290. figure.setAttribute('embedded', true);
  291. figure.addEventListener("click", function (event) {
  292. if (!event.ctrlKey && !event.target.id.includes("favbutton") && event.target.type != "checkbox") {
  293. if (event.target.href)
  294. return;
  295. else
  296. event.preventDefault();
  297. if (!isShowing)
  298. showImage(figure);
  299. }
  300. });
  301. }
  302. }
  303.  
  304. async function showImage(figure) {
  305. isShowing = true;
  306. embeddedImage = new EmbeddedImage(figure);
  307. embeddedImage.onRemove(() => {
  308. embeddedImage = null;
  309. isShowing = false;
  310. });
  311. }
  312.  
  313. function downloadImage() {
  314. console.log("Embedded Image Viewer downloading Image...");
  315. let url = window.location.toString();
  316. if (url.includes("?")) {
  317. const parts = url.split('?');
  318. url = parts[0];
  319. }
  320. const download = document.createElement('a');
  321. download.href = url;
  322. download.download = url.substring(url.lastIndexOf("/") + 1);
  323. download.style.display = 'none';
  324. document.body.appendChild(download);
  325. download.click();
  326. document.body.removeChild(download);
  327.  
  328. window.close();
  329. }