SubDL Image Preview

Display image previews on subdl.com .

  1. // ==UserScript==
  2. // @name SubDL Image Preview
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.2
  5. // @description Display image previews on subdl.com .
  6. // @author dr.bobo0
  7. // @match https://subdl.com/u/*
  8. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAgCAMAAADdXFNzAAAAXVBMVEVHcEz/7ir/7ir/7ir/7ir/7ir/7ir/7in/7ir/7ir/7ir/7ir/7Sr/7ir/7ir/7ir/9Sn/8iopKjMxMTMdIDMKEDSDfDBkXzH/+inBtS3bzSzSxC1GRDLs3SuZkC8BXe1rAAAAD3RSTlMAmpG5qyvRFvBGY9wGycP/EwDKAAABG0lEQVQokX2Ti5KDIAxFUSmgtg1P6/v/P3NFB0y03TvjjPFAIjeEsSzZ8FaBankj2V21gFOivtDnG6jeT4wfcNcD5f6CAer/duMMPzDAgXmOtYnSJsWcZl982DXmBbFClXd31lm7PR+dPlW4euRd12G+/UGDue11PwfEG1Zg7hdjpumsDwUTmLvZwHaC84SCqfxuxrDV7okDCpujh+D8ShcQ8/rVuzBowhWO9Me6sKD6ir1IOpidcyh8sZJgs3jiT4l7r7UGM3nMa+qv1aafqb9neyMfpxHz2GCJuPPeO3fy/aKnDpgh+KiwJl4cFyhZYKZhV8IqDU4+3SGS/fcFRgMg1Y0qOoTFBRfX+ZQcUf5tglldVqIVVYmH9w/WzDC9Fj6LqQAAAABJRU5ErkJggg==
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. console.log("UserScript started.");
  16.  
  17. const storagePrefix = "subdl_image_cache_";
  18. const maxCacheAge = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds
  19.  
  20. const exclusionList = [
  21. '/', // Home
  22. '/panel',
  23. '/panel/my-subtitles',
  24. '/panel/account',
  25. '/panel/api',
  26. '/latest',
  27. '/popular',
  28. 'https://t.me/subdl_com', // External link
  29. '/ads', // Advertise link
  30. '/api-doc', // API documentation
  31. '/panel/logout',
  32. '/login', // Login page
  33. '#', // Placeholder links like Privacy Policy
  34. '/signup' // Signup page
  35. ];
  36.  
  37. function clearOldCache() {
  38. console.log("Clearing old cache entries.");
  39. const now = Date.now();
  40. for (let i = 0; i < localStorage.length; i++) {
  41. const key = localStorage.key(i);
  42. if (key && key.startsWith(storagePrefix)) {
  43. try {
  44. const item = JSON.parse(localStorage.getItem(key));
  45. if (now - item.timestamp > maxCacheAge) {
  46. localStorage.removeItem(key);
  47. console.log(`Removed old cache entry: ${key}`);
  48. }
  49. } catch (e) {
  50. console.error(`Error parsing cache item: ${key}`, e);
  51. localStorage.removeItem(key);
  52. }
  53. }
  54. }
  55. }
  56.  
  57. function addPreviewToLinks() {
  58. console.log("Adding preview to links.");
  59. const links = document.querySelectorAll('a[href*="/s/info/"]');
  60. let linksFound = 0;
  61.  
  62. links.forEach(link => {
  63. if (shouldAddPreview(link)) {
  64. linksFound++;
  65. if (!link.dataset.hasEventListener) {
  66. console.log("Adding event listener to link:", link.href);
  67. link.dataset.hasEventListener = 'true';
  68.  
  69. link.addEventListener("mouseover", function() {
  70. console.log("Mouseover event triggered for link:", this.href);
  71. let previewContainer = createPreviewContainer();
  72.  
  73. document.body.appendChild(previewContainer);
  74. showLoadingSpinner(previewContainer);
  75. fetchImage(this.href, previewContainer);
  76.  
  77. let removeMousemoveListener = addMousemoveListener(previewContainer);
  78. handleMouseout(link, previewContainer, removeMousemoveListener);
  79. handleClick(link, previewContainer, removeMousemoveListener);
  80. });
  81. } else {
  82. console.log("Event listener already added for link:", link.href);
  83. }
  84. }
  85. });
  86.  
  87. if (linksFound === 0) {
  88. console.warn("No suitable links found.");
  89. }
  90. }
  91.  
  92. function shouldAddPreview(link) {
  93. const href = link.href;
  94.  
  95. // Check if the link is in the exclusion list
  96. for (const exclusion of exclusionList) {
  97. if (href.endsWith(exclusion)) {
  98. return false;
  99. }
  100. }
  101.  
  102. const pattern = /subdl.com/; // General pattern to match subdl.com links
  103. return pattern.test(href);
  104. }
  105.  
  106. function createPreviewContainer() {
  107. console.log("Creating preview container.");
  108. let previewContainer = document.createElement("div");
  109. Object.assign(previewContainer.style, {
  110. position: "fixed",
  111. display: "none",
  112. transition: "opacity 0.1s ease-in-out",
  113. opacity: 0,
  114. width: "154px",
  115. height: "231px",
  116. overflow: "hidden",
  117. zIndex: 1000,
  118. borderRadius: "8px",
  119. boxShadow: "0 4px 8px rgba(0,0,0,0.2)",
  120. backgroundColor: "#ffffff"
  121. });
  122. console.log("Preview container created:", previewContainer);
  123. return previewContainer;
  124. }
  125.  
  126. function showLoadingSpinner(previewContainer) {
  127. console.log("Showing loading spinner.");
  128. previewContainer.innerHTML = `
  129. <div style="display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; background-color: #f0f0f0;">
  130. <div style="width: 40px; height: 40px; border: 4px solid #333; border-top: 4px solid #999; border-radius: 50%; animation: spin 1s linear infinite;"></div>
  131. </div>
  132. <style>
  133. @keyframes spin {
  134. 0% { transform: rotate(0deg); }
  135. 100% { transform: rotate(360deg); }
  136. }
  137. </style>
  138. `;
  139. previewContainer.style.display = "block";
  140. previewContainer.style.opacity = 1;
  141. }
  142.  
  143. function fetchImage(url, previewContainer) {
  144. console.log("Fetching image for URL:", url);
  145. const cacheKey = storagePrefix + url;
  146. let cachedImage = localStorage.getItem(cacheKey);
  147.  
  148. if (cachedImage) {
  149. try {
  150. const parsedCache = JSON.parse(cachedImage);
  151. if (Date.now() - parsedCache.timestamp < maxCacheAge) {
  152. console.log(`Image found in cache for ${url}`);
  153. setImage(previewContainer, parsedCache.src);
  154. return;
  155. } else {
  156. console.log(`Cached image for ${url} is too old, fetching new one`);
  157. localStorage.removeItem(cacheKey);
  158. }
  159. } catch (e) {
  160. console.error(`Error parsing cached image for ${url}`, e);
  161. localStorage.removeItem(cacheKey);
  162. }
  163. }
  164.  
  165. console.log(`Image not in cache, fetching from network for ${url}`);
  166. fetch(url)
  167. .then(response => response.text())
  168. .then(html => {
  169. console.log("Fetched HTML for URL:", url);
  170. let parser = new DOMParser();
  171. let doc = parser.parseFromString(html, 'text/html');
  172. let preview = doc.querySelector("div.select-none img"); // New image selector
  173. if (preview) {
  174. let src = preview.getAttribute("src");
  175. console.log(`Image fetched successfully for ${url}`);
  176. setImage(previewContainer, src);
  177. try {
  178. localStorage.setItem(cacheKey, JSON.stringify({
  179. src: src,
  180. timestamp: Date.now()
  181. }));
  182. } catch (e) {
  183. console.error(`Failed to cache image for ${url}:`, e);
  184. clearOldCache(); // Attempt to free up space
  185. }
  186. } else {
  187. console.log(`Image not found in the fetched HTML for ${url}`);
  188. setError(previewContainer, "Image not found.");
  189. }
  190. })
  191. .catch(error => {
  192. console.error(`Failed to fetch image for ${url}: ${error}`);
  193. setError(previewContainer, "Failed to load image.");
  194. });
  195. }
  196.  
  197. function setImage(previewContainer, src) {
  198. console.log("Setting image for preview container:", src);
  199. previewContainer.innerHTML = `<img style="width: 100%; height: 100%; object-fit: cover;" src="${src}"/>`;
  200. previewContainer.style.display = "block";
  201. previewContainer.style.opacity = 1;
  202. console.log("Image displayed in preview container.");
  203. }
  204.  
  205. function setError(previewContainer, message) {
  206. console.log("Setting error message for preview container:", message);
  207. previewContainer.innerHTML = `
  208. <div style="display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; color: red; font-weight: bold; text-align: center;">
  209. ${message}
  210. </div>
  211. `;
  212. previewContainer.style.display = "block";
  213. previewContainer.style.opacity = 1;
  214. console.log("Error message displayed in preview container.");
  215. }
  216.  
  217. function addMousemoveListener(previewContainer) {
  218. console.log("Adding mousemove listener for preview container.");
  219. function movePreview(event) {
  220. previewContainer.style.top = event.clientY + 20 + "px";
  221. previewContainer.style.left = event.clientX + 20 + "px";
  222.  
  223. if (event.clientX + previewContainer.offsetWidth + 20 > window.innerWidth) {
  224. previewContainer.style.left = window.innerWidth - previewContainer.offsetWidth - 20 + "px";
  225. }
  226. if (event.clientY + previewContainer.offsetHeight + 20 > window.innerHeight) {
  227. previewContainer.style.top = window.innerHeight - previewContainer.offsetHeight - 20 + "px";
  228. }
  229. }
  230.  
  231. document.addEventListener("mousemove", movePreview);
  232. console.log("Mousemove listener added.");
  233.  
  234. return () => {
  235. document.removeEventListener("mousemove", movePreview);
  236. console.log("Mousemove listener removed.");
  237. };
  238. }
  239.  
  240. function handleMouseout(link, previewContainer, removeMousemoveListener) {
  241. console.log("Adding mouseout listener for link:", link.href);
  242. link.addEventListener("mouseout", function() {
  243. console.log("Mouseout event triggered for link:", link.href);
  244. cleanupPreview(previewContainer, removeMousemoveListener);
  245. });
  246. }
  247.  
  248. function handleClick(link, previewContainer, removeMousemoveListener) {
  249. console.log("Adding click listener for link:", link.href);
  250. link.addEventListener("click", function() {
  251. console.log("Click event triggered for link:", link.href);
  252. cleanupPreview(previewContainer, removeMousemoveListener);
  253. });
  254. }
  255.  
  256. function cleanupPreview(previewContainer, removeMousemoveListener) {
  257. previewContainer.style.opacity = 0;
  258. setTimeout(() => {
  259. previewContainer.style.display = "none";
  260. previewContainer.remove();
  261. removeMousemoveListener();
  262. console.log("Preview container removed.");
  263. }, 200);
  264. }
  265.  
  266. // Clear old cache entries and set up the DOM mutation observer
  267. clearOldCache();
  268. addPreviewToLinks();
  269.  
  270. const observer = new MutationObserver(() => {
  271. console.log("DOM mutation detected, adding preview to new links.");
  272. addPreviewToLinks();
  273. });
  274. observer.observe(document.body, { childList: true, subtree: true });
  275.  
  276. })();