FA Embedded Image Viewer

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

Verze ze dne 18. 02. 2024. Zobrazit nejnovější verzi.

  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/1326313/Furaffinity-Match-List.js
  10. // @grant none
  11. // @version 2.0.12
  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 openInNewTabSetting = CustomSettings.newSetting("Open in new Tab", "Sets wether to open links in a new Tab or the current one.", SettingTypes.Boolean, "Open in new Tab", true);
  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. return;
  35.  
  36. const page = new CustomPage("d.furaffinity.net", "eidownload");
  37. page.onopen = (data) => {
  38. downloadImage();
  39. return;
  40. };
  41.  
  42. if (matchList.isWindowIFrame() == true)
  43. return;
  44.  
  45. const requestHelper = new FARequestHelper(2);
  46.  
  47. class EmbeddedImage {
  48. constructor(figure) {
  49. this.embeddedElem;
  50. this.backgroundElem;
  51. this.submissionContainer;
  52. this.submissionImg;
  53. this.buttonsContainer;
  54. this.favButton;
  55. this.downloadButton;
  56. this.closeButton;
  57.  
  58. this.favRequestRunning = false;
  59. this.downloadRequestRunning = false;
  60.  
  61. this._onRemoveAction;
  62.  
  63. this.createStyle();
  64. this.createElements(figure);
  65.  
  66. this.loadingSpinner = new LoadingSpinner(this.submissionContainer);
  67. this.loadingSpinner.delay = loadingSpinSpeedSetting.value;
  68. this.loadingSpinner.spinnerThickness = 6;
  69. this.loadingSpinner.visible = true;
  70. this.fillSubDocInfos(figure);
  71. }
  72.  
  73. createStyle() {
  74. if (document.getElementById("embeddedStyle")) return;
  75. const style = document.createElement("style");
  76. style.id = "embeddedStyle";
  77. style.type = "text/css";
  78. style.innerHTML = `
  79. #embeddedElem {
  80. position: fixed;
  81. width: 100vw;
  82. height: 100vh;
  83. max-width: 1850px;
  84. z-index: 999999;
  85. background: rgba(30,33,38,.65);
  86. }
  87. #embeddedBackgroundElem {
  88. position: fixed;
  89. display: flex;
  90. flex-direction: column;
  91. left: 50%;
  92. transform: translate(-50%, 0%);
  93. margin-top: 20px;
  94. padding: 20px;
  95. background: rgba(30,33,38,.90);
  96. border-radius: 10px;
  97. }
  98. #embeddedSubmissionImg {
  99. max-width: inherit;
  100. max-height: inherit;
  101. border-radius: 10px;
  102. }
  103. #embeddedButtonsContainer {
  104. margin-top: 20px;
  105. margin-bottom: 20px;
  106. margin-left: 20px;
  107. }
  108. .embeddedButton {
  109. margin-left: 4px;
  110. margin-right: 4px;
  111. user-select: none;
  112. }
  113. `;
  114. document.head.appendChild(style);
  115. }
  116.  
  117. onRemove(action) {
  118. this._onRemoveAction = action;
  119. }
  120.  
  121. remove() {
  122. this.embeddedElem.parentNode.removeChild(this.embeddedElem);
  123. if (this._onRemoveAction)
  124. this._onRemoveAction();
  125. }
  126.  
  127. createElements(figure) {
  128. this.embeddedElem = document.createElement("div");
  129. this.embeddedElem.id = "embeddedElem";
  130. this.embeddedElem.onclick = (event) => {
  131. if (event.target == this.embeddedElem)
  132. this.remove();
  133. };
  134.  
  135. this.backgroundElem = document.createElement("div");
  136. this.backgroundElem.id = "embeddedBackgroundElem";
  137. notClosingElemsArr.push(this.backgroundElem.id);
  138.  
  139. this.submissionContainer = document.createElement("a");
  140. this.submissionContainer.id = "embeddedSubmissionContainer";
  141. notClosingElemsArr.push(this.submissionContainer.id);
  142.  
  143. this.backgroundElem.appendChild(this.submissionContainer);
  144.  
  145. this.buttonsContainer = document.createElement("div");
  146. this.buttonsContainer.id = "embeddedButtonsContainer";
  147. notClosingElemsArr.push(this.buttonsContainer.id);
  148.  
  149. this.favButton = document.createElement("a");
  150. this.favButton.id = "embeddedFavButton";
  151. notClosingElemsArr.push(this.favButton.id);
  152. this.favButton.type = "button";
  153. this.favButton.className = "embeddedButton button standard mobile-fix";
  154. this.favButton.textContent = "⠀⠀";
  155. this.buttonsContainer.appendChild(this.favButton);
  156.  
  157. this.downloadButton = document.createElement("a");
  158. this.downloadButton.id = "embeddedDownloadButton";
  159. notClosingElemsArr.push(this.downloadButton.id);
  160. this.downloadButton.type = "button";
  161. this.downloadButton.className = "embeddedButton button standard mobile-fix";
  162. this.downloadButton.textContent = "Download";
  163. this.buttonsContainer.appendChild(this.downloadButton);
  164.  
  165. const byLink = getByLinkFromFigcaption(figure.querySelector("figcaption"));
  166. if (byLink) {
  167. this.openGalleryButton = document.createElement("a");
  168. this.openGalleryButton.id = "embeddedOpenGalleryButton";
  169. notClosingElemsArr.push(this.openGalleryButton.id);
  170. this.openGalleryButton.type = "button";
  171. this.openGalleryButton.className = "embeddedButton button standard mobile-fix";
  172. this.openGalleryButton.textContent = "Open Gallery";
  173. const galleryLink = byLink.replace("user", "gallery");
  174. this.openGalleryButton.href = galleryLink;
  175. if (openInNewTabSetting.value == true)
  176. this.openGalleryButton.target = "_blank";
  177. this.buttonsContainer.appendChild(this.openGalleryButton);
  178. }
  179.  
  180. this.openButton = document.createElement("a");
  181. this.openButton.id = "embeddedOpenButton";
  182. notClosingElemsArr.push(this.openButton.id);
  183. this.openButton.type = "button";
  184. this.openButton.className = "embeddedButton button standard mobile-fix";
  185. this.openButton.textContent = "Open";
  186. const link = figure.querySelector("a[href]");
  187. this.openButton.href = link;
  188. if (openInNewTabSetting.value == true)
  189. this.openButton.target = "_blank";
  190. this.buttonsContainer.appendChild(this.openButton);
  191.  
  192. this.closeButton = document.createElement("a");
  193. this.closeButton.id = "embeddedCloseButton";
  194. notClosingElemsArr.push(this.closeButton.id);
  195. this.closeButton.type = "button";
  196. this.closeButton.className = "embeddedButton button standard mobile-fix";
  197. this.closeButton.textContent = "Close";
  198. this.closeButton.onclick = () => this.remove();
  199. this.buttonsContainer.appendChild(this.closeButton);
  200.  
  201. this.backgroundElem.appendChild(this.buttonsContainer);
  202.  
  203. this.embeddedElem.appendChild(this.backgroundElem);
  204.  
  205. const ddmenu = document.getElementById("ddmenu");
  206. ddmenu.appendChild(this.embeddedElem);
  207. }
  208.  
  209. async fillSubDocInfos(figure) {
  210. const sid = figure.id.split("-")[1];
  211. const ddmenu = document.getElementById("ddmenu");
  212. const doc = await requestHelper.SubmissionRequests.getSubmissionPage(sid);
  213. if (this.loadingSpinner)
  214. this.loadingSpinner.visible = false;
  215. if (doc) {
  216. this.submissionImg = doc.getElementById("submissionImg");
  217. this.submissionImg.style.maxWidth = window.innerWidth - 20 * 2 + "px";
  218. this.submissionImg.style.maxHeight = window.innerHeight - ddmenu.clientHeight - 38 * 2 - 20 * 2 - 100 + "px";
  219. this.submissionContainer.appendChild(this.submissionImg);
  220. this.submissionContainer.href = doc.querySelector('meta[property="og:url"]').content;
  221.  
  222. const result = getFavKey(doc);
  223. this.favButton.textContent = result.isFav ? "+Fav" : "-Fav";
  224. this.favButton.setAttribute("isFav", result.isFav);
  225. this.favButton.setAttribute("key", result.favKey);
  226. this.favButton.onclick = () => {
  227. if (this.favRequestRunning == false)
  228. this.doFavRequest(sid);
  229. };
  230.  
  231. this.downloadButton.onclick = () => {
  232. if (this.downloadRequestRunning == true)
  233. return;
  234. this.downloadRequestRunning = true;
  235. const loadingTextSpinner = new LoadingTextSpinner(this.downloadButton);
  236. loadingTextSpinner.delay = loadingSpinSpeedFavSetting.value;
  237. loadingTextSpinner.visible = true;
  238. const iframe = document.createElement("iframe");
  239. iframe.style.display = "none";
  240. iframe.src = this.submissionImg.src + "?eidownload";
  241. iframe.onload = () => {
  242. this.downloadRequestRunning = false;
  243. loadingTextSpinner.visible = false;
  244. setTimeout(() => iframe.parentNode.removeChild(iframe), 100);
  245. };
  246. document.body.appendChild(iframe);
  247. };
  248. }
  249. }
  250.  
  251. async doFavRequest(sid) {
  252. this.favRequestRunning = true;
  253. const loadingTextSpinner = new LoadingTextSpinner(this.favButton);
  254. loadingTextSpinner.delay = loadingSpinSpeedFavSetting.value;
  255. loadingTextSpinner.visible = true;
  256. let favKey = this.favButton.getAttribute("key");
  257. let isFav = this.favButton.getAttribute("isFav");
  258. if (isFav == "true") {
  259. favKey = await requestHelper.SubmissionRequests.favSubmission(sid, favKey);
  260. loadingTextSpinner.visible = false;
  261. if (favKey) {
  262. this.favButton.setAttribute("key", favKey);
  263. isFav = false;
  264. this.favButton.setAttribute("isFav", isFav);
  265. this.favButton.textContent = "-Fav";
  266. } else {
  267. this.favButton.textContent = "x";
  268. setTimeout(() => this.favButton.textContent = "+Fav", 1000);
  269. }
  270. } else {
  271. favKey = await requestHelper.SubmissionRequests.unfavSubmission(sid, favKey);
  272. loadingTextSpinner.visible = false;
  273. if (favKey) {
  274. this.favButton.setAttribute("key", favKey);
  275. isFav = true;
  276. this.favButton.setAttribute("isFav", isFav);
  277. this.favButton.textContent = "+Fav";
  278. } else {
  279. this.favButton.textContent = "x";
  280. setTimeout(() => this.favButton.textContent = "-Fav", 1000);
  281. }
  282. }
  283. this.favRequestRunning = false;
  284. }
  285. }
  286.  
  287. function getByLinkFromFigcaption(figcaption) {
  288. if (figcaption) {
  289. const infos = figcaption.querySelectorAll("i");
  290. let byLink;
  291. for (const info of infos) {
  292. if (info.textContent.toLowerCase().includes("by")) {
  293. const linkElem = info.parentNode.querySelector("a[href][title]");
  294. if (linkElem)
  295. byLink = linkElem.href;
  296. }
  297. }
  298. return byLink;
  299. }
  300. }
  301.  
  302. function getFavKey(doc) {
  303. const columnPage = doc.getElementById("columnpage");
  304. const navbar = columnPage.querySelector('div[class*="favorite-nav"');
  305. const buttons = navbar.querySelectorAll('a[class*="button"][href]');
  306. let favButton;
  307. for (const button of buttons) {
  308. if (button.textContent.toLowerCase().includes("fav"))
  309. favButton = button;
  310. }
  311.  
  312. if (favButton) {
  313. const favKey = favButton.href.split("?key=")[1];
  314. const isFav = !favButton.href.toLowerCase().includes("unfav");
  315. return { favKey, isFav };
  316. }
  317. }
  318.  
  319. let isShowing = false;
  320. let notClosingElemsArr = [];
  321. let embeddedImage;
  322.  
  323. addEmbedded();
  324. window.updateEmbedded = addEmbedded;
  325.  
  326. document.addEventListener("click", (event) => {
  327. if (event.target.parentNode instanceof HTMLDocument && embeddedImage)
  328. embeddedImage.remove();
  329. })
  330.  
  331. async function addEmbedded() {
  332. for (const figure of document.querySelectorAll('figure:not([embedded])')) {
  333. figure.setAttribute('embedded', true);
  334. figure.addEventListener("click", function (event) {
  335. if (!event.ctrlKey && !event.target.id.includes("favbutton") && event.target.type != "checkbox") {
  336. if (event.target.href)
  337. return;
  338. else
  339. event.preventDefault();
  340. if (!isShowing)
  341. showImage(figure);
  342. }
  343. });
  344. }
  345. }
  346.  
  347. async function showImage(figure) {
  348. isShowing = true;
  349. embeddedImage = new EmbeddedImage(figure);
  350. embeddedImage.onRemove(() => {
  351. embeddedImage = null;
  352. isShowing = false;
  353. });
  354. }
  355.  
  356. function downloadImage() {
  357. console.log("Embedded Image Viewer downloading Image...");
  358. let url = window.location.toString();
  359. if (url.includes("?")) {
  360. const parts = url.split('?');
  361. url = parts[0];
  362. }
  363. const download = document.createElement('a');
  364. download.href = url;
  365. download.download = url.substring(url.lastIndexOf("/") + 1);
  366. download.style.display = 'none';
  367. document.body.appendChild(download);
  368. download.click();
  369. document.body.removeChild(download);
  370.  
  371. window.close();
  372. }