- // ==UserScript==
- // @name SubDL Image Preview
- // @namespace http://tampermonkey.net/
- // @version 2.2
- // @description Display image previews on subdl.com .
- // @author dr.bobo0
- // @match https://subdl.com/u/*
- // @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==
- // @grant none
- // ==/UserScript==
-
- (function() {
- 'use strict';
-
- console.log("UserScript started.");
-
- const storagePrefix = "subdl_image_cache_";
- const maxCacheAge = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds
-
- const exclusionList = [
- '/', // Home
- '/panel',
- '/panel/my-subtitles',
- '/panel/account',
- '/panel/api',
- '/latest',
- '/popular',
- 'https://t.me/subdl_com', // External link
- '/ads', // Advertise link
- '/api-doc', // API documentation
- '/panel/logout',
- '/login', // Login page
- '#', // Placeholder links like Privacy Policy
- '/signup' // Signup page
- ];
-
- function clearOldCache() {
- console.log("Clearing old cache entries.");
- const now = Date.now();
- for (let i = 0; i < localStorage.length; i++) {
- const key = localStorage.key(i);
- if (key && key.startsWith(storagePrefix)) {
- try {
- const item = JSON.parse(localStorage.getItem(key));
- if (now - item.timestamp > maxCacheAge) {
- localStorage.removeItem(key);
- console.log(`Removed old cache entry: ${key}`);
- }
- } catch (e) {
- console.error(`Error parsing cache item: ${key}`, e);
- localStorage.removeItem(key);
- }
- }
- }
- }
-
- function addPreviewToLinks() {
- console.log("Adding preview to links.");
- const links = document.querySelectorAll('a[href*="/s/info/"]');
- let linksFound = 0;
-
- links.forEach(link => {
- if (shouldAddPreview(link)) {
- linksFound++;
- if (!link.dataset.hasEventListener) {
- console.log("Adding event listener to link:", link.href);
- link.dataset.hasEventListener = 'true';
-
- link.addEventListener("mouseover", function() {
- console.log("Mouseover event triggered for link:", this.href);
- let previewContainer = createPreviewContainer();
-
- document.body.appendChild(previewContainer);
- showLoadingSpinner(previewContainer);
- fetchImage(this.href, previewContainer);
-
- let removeMousemoveListener = addMousemoveListener(previewContainer);
- handleMouseout(link, previewContainer, removeMousemoveListener);
- handleClick(link, previewContainer, removeMousemoveListener);
- });
- } else {
- console.log("Event listener already added for link:", link.href);
- }
- }
- });
-
- if (linksFound === 0) {
- console.warn("No suitable links found.");
- }
- }
-
- function shouldAddPreview(link) {
- const href = link.href;
-
- // Check if the link is in the exclusion list
- for (const exclusion of exclusionList) {
- if (href.endsWith(exclusion)) {
- return false;
- }
- }
-
- const pattern = /subdl.com/; // General pattern to match subdl.com links
- return pattern.test(href);
- }
-
- function createPreviewContainer() {
- console.log("Creating preview container.");
- let previewContainer = document.createElement("div");
- Object.assign(previewContainer.style, {
- position: "fixed",
- display: "none",
- transition: "opacity 0.1s ease-in-out",
- opacity: 0,
- width: "154px",
- height: "231px",
- overflow: "hidden",
- zIndex: 1000,
- borderRadius: "8px",
- boxShadow: "0 4px 8px rgba(0,0,0,0.2)",
- backgroundColor: "#ffffff"
- });
- console.log("Preview container created:", previewContainer);
- return previewContainer;
- }
-
- function showLoadingSpinner(previewContainer) {
- console.log("Showing loading spinner.");
- previewContainer.innerHTML = `
- <div style="display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; background-color: #f0f0f0;">
- <div style="width: 40px; height: 40px; border: 4px solid #333; border-top: 4px solid #999; border-radius: 50%; animation: spin 1s linear infinite;"></div>
- </div>
- <style>
- @keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
- </style>
- `;
- previewContainer.style.display = "block";
- previewContainer.style.opacity = 1;
- }
-
- function fetchImage(url, previewContainer) {
- console.log("Fetching image for URL:", url);
- const cacheKey = storagePrefix + url;
- let cachedImage = localStorage.getItem(cacheKey);
-
- if (cachedImage) {
- try {
- const parsedCache = JSON.parse(cachedImage);
- if (Date.now() - parsedCache.timestamp < maxCacheAge) {
- console.log(`Image found in cache for ${url}`);
- setImage(previewContainer, parsedCache.src);
- return;
- } else {
- console.log(`Cached image for ${url} is too old, fetching new one`);
- localStorage.removeItem(cacheKey);
- }
- } catch (e) {
- console.error(`Error parsing cached image for ${url}`, e);
- localStorage.removeItem(cacheKey);
- }
- }
-
- console.log(`Image not in cache, fetching from network for ${url}`);
- fetch(url)
- .then(response => response.text())
- .then(html => {
- console.log("Fetched HTML for URL:", url);
- let parser = new DOMParser();
- let doc = parser.parseFromString(html, 'text/html');
- let preview = doc.querySelector("div.select-none img"); // New image selector
- if (preview) {
- let src = preview.getAttribute("src");
- console.log(`Image fetched successfully for ${url}`);
- setImage(previewContainer, src);
- try {
- localStorage.setItem(cacheKey, JSON.stringify({
- src: src,
- timestamp: Date.now()
- }));
- } catch (e) {
- console.error(`Failed to cache image for ${url}:`, e);
- clearOldCache(); // Attempt to free up space
- }
- } else {
- console.log(`Image not found in the fetched HTML for ${url}`);
- setError(previewContainer, "Image not found.");
- }
- })
- .catch(error => {
- console.error(`Failed to fetch image for ${url}: ${error}`);
- setError(previewContainer, "Failed to load image.");
- });
- }
-
- function setImage(previewContainer, src) {
- console.log("Setting image for preview container:", src);
- previewContainer.innerHTML = `<img style="width: 100%; height: 100%; object-fit: cover;" src="${src}"/>`;
- previewContainer.style.display = "block";
- previewContainer.style.opacity = 1;
- console.log("Image displayed in preview container.");
- }
-
- function setError(previewContainer, message) {
- console.log("Setting error message for preview container:", message);
- previewContainer.innerHTML = `
- <div style="display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; color: red; font-weight: bold; text-align: center;">
- ${message}
- </div>
- `;
- previewContainer.style.display = "block";
- previewContainer.style.opacity = 1;
- console.log("Error message displayed in preview container.");
- }
-
- function addMousemoveListener(previewContainer) {
- console.log("Adding mousemove listener for preview container.");
- function movePreview(event) {
- previewContainer.style.top = event.clientY + 20 + "px";
- previewContainer.style.left = event.clientX + 20 + "px";
-
- if (event.clientX + previewContainer.offsetWidth + 20 > window.innerWidth) {
- previewContainer.style.left = window.innerWidth - previewContainer.offsetWidth - 20 + "px";
- }
- if (event.clientY + previewContainer.offsetHeight + 20 > window.innerHeight) {
- previewContainer.style.top = window.innerHeight - previewContainer.offsetHeight - 20 + "px";
- }
- }
-
- document.addEventListener("mousemove", movePreview);
- console.log("Mousemove listener added.");
-
- return () => {
- document.removeEventListener("mousemove", movePreview);
- console.log("Mousemove listener removed.");
- };
- }
-
- function handleMouseout(link, previewContainer, removeMousemoveListener) {
- console.log("Adding mouseout listener for link:", link.href);
- link.addEventListener("mouseout", function() {
- console.log("Mouseout event triggered for link:", link.href);
- cleanupPreview(previewContainer, removeMousemoveListener);
- });
- }
-
- function handleClick(link, previewContainer, removeMousemoveListener) {
- console.log("Adding click listener for link:", link.href);
- link.addEventListener("click", function() {
- console.log("Click event triggered for link:", link.href);
- cleanupPreview(previewContainer, removeMousemoveListener);
- });
- }
-
- function cleanupPreview(previewContainer, removeMousemoveListener) {
- previewContainer.style.opacity = 0;
- setTimeout(() => {
- previewContainer.style.display = "none";
- previewContainer.remove();
- removeMousemoveListener();
- console.log("Preview container removed.");
- }, 200);
- }
-
- // Clear old cache entries and set up the DOM mutation observer
- clearOldCache();
- addPreviewToLinks();
-
- const observer = new MutationObserver(() => {
- console.log("DOM mutation detected, adding preview to new links.");
- addPreviewToLinks();
- });
- observer.observe(document.body, { childList: true, subtree: true });
-
- })();