FA Embedded Image Viewer

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

当前为 2024-02-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name FA Embedded Image Viewer
  3. // @namespace Violentmonkey Scripts
  4. // @match *://*.furaffinity.net/*
  5. // @require https://update.greasyfork.org/scripts/475041/1267274/Furaffinity-Custom-Settings.js
  6. // @require https://update.greasyfork.org/scripts/483952/1306729/Furaffinity-Request-Helper.js
  7. // @require https://update.greasyfork.org/scripts/485153/1316289/Furaffinity-Loading-Animations.js
  8. // @require https://update.greasyfork.org/scripts/476762/1318215/Furaffinity-Custom-Pages.js
  9. // @require https://update.greasyfork.org/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://greasyfork.org/de/scripts/458971-embedded-image-viewer
  16. // @supportURL https://greasyfork.org/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. }