Greasy Fork is available in English.

oxtorrent preview Image with Cache Management

Show image preview next to the titles by hovering the mouse, with caching and cleanup.

// ==UserScript==
// @name         oxtorrent preview Image with Cache Management
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  Show image preview next to the titles by hovering the mouse, with caching and cleanup.
// @author       dr.bobo0
// @match        https://www.oxtorrent.co/*
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAAAAAAeW/F+AAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAACYktHRAD/h4/MvwAAAAd0SU1FB+cFERYzNsSqEXsAAADBSURBVCjPY7RiwAeYGGgozQKhPHjiUcUXvjjCwMDAwGjFwMCgUi6BqfFC5xcGBmZZBgaefiEs5krwnIbY7cGD1V4PCYi0NQ532UCcpgLhHVl7ByocD3cKC1zti04Y68iXZkx/H0FyNJZg+Ypk6wvqBCqdpLkRgjwSmNI2CGkDLLolyqGBy+CBiHsWBgaGOxBxGyT9yLovMGAHRyDSO79gld3xApJavtzVwpIgLkz9BU1rDAwh4h6okshJkfaBihUAAMGoJCE4fyJaAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIzLTA1LTE3VDIyOjUxOjUzKzAwOjAwF57XXQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMy0wNS0xN1QyMjo1MTo1MyswMDowMGbDb+EAAAAodEVYdGRhdGU6dGltZXN0YW1wADIwMjMtMDUtMTdUMjI6NTE6NTQrMDA6MDD0cXCwAAAAAElFTkSuQmCC
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const CACHE_TTL = 7 * 24 * 60 * 60 * 1000; // Cache Time To Live: 7 days

    // Function to remove old cache entries that do not have expiration timestamps
    function cleanOldCache() {
        for (let i = 0; i < localStorage.length; i++) {
            let key = localStorage.key(i);
            if (key.startsWith("oxtorrent_cache_")) {
                let cacheEntry = JSON.parse(localStorage.getItem(key));
                if (!cacheEntry.timestamp) {
                    localStorage.removeItem(key);
                }
            }
        }
    }

    // Function to fetch from cache with expiration check
    function getFromCache(url) {
        let cacheKey = "oxtorrent_cache_" + url;
        let cachedData = localStorage.getItem(cacheKey);
        if (cachedData) {
            let cacheEntry = JSON.parse(cachedData);
            if (Date.now() - cacheEntry.timestamp < CACHE_TTL) {
                return cacheEntry.data;
            } else {
                localStorage.removeItem(cacheKey); // Remove expired cache
            }
        }
        return null;
    }

    // Function to save to cache with timestamp
    function saveToCache(url, data) {
        let cacheKey = "oxtorrent_cache_" + url;
        let cacheEntry = {
            data: data,
            timestamp: Date.now()
        };
        localStorage.setItem(cacheKey, JSON.stringify(cacheEntry));
    }

    cleanOldCache();

    document.querySelectorAll("td > a[href^='/torrent/']").forEach(link => {
        link.addEventListener("mouseover", function(event) {
            let previewContainer = document.createElement("div");
            previewContainer.style.position = "fixed";
            previewContainer.style.display = "none";
            previewContainer.style.transition = "opacity 0.1s ease-in-out";
            previewContainer.style.opacity = 0;
            previewContainer.style.width = "216px";
            previewContainer.style.height = "307px";
            previewContainer.style.overflow = "hidden";
            document.body.appendChild(previewContainer);

            let url = this.href;
            let cachedImage = getFromCache(url);

            if (cachedImage) {
                previewContainer.innerHTML = `<img style="width: 100%; height: 100%;" src="${cachedImage}"/>`;
                showPreview(previewContainer);
            } else {
                var xhr = new XMLHttpRequest();
                xhr.open("GET", url);
                xhr.responseType = "document";

                xhr.onload = function() {
                    let preview = xhr.response.querySelector("#torrentsimage img");
                    if (preview) {
                        let imgSrc = preview.getAttribute("src");
                        previewContainer.innerHTML = `<img style="width: 100%; height: 100%;" src="${imgSrc}"/>`;
                        saveToCache(url, imgSrc);
                    }
                    showPreview(previewContainer);
                };

                xhr.send();
            }

            link.addEventListener("mouseout", function() {
                previewContainer.style.opacity = 0;
                setTimeout(function () {
                    previewContainer.style.display = "none";
                    previewContainer.remove();
                }, 300);
            });
        });
    });

    function showPreview(previewContainer) {
        document.addEventListener("mousemove", function (event) {
            previewContainer.style.top = event.clientY + 20 + "px";
            previewContainer.style.left = event.clientX + 20 + "px";

            if (previewContainer.getBoundingClientRect().right > window.innerWidth) {
                previewContainer.style.left = (window.innerWidth - previewContainer.offsetWidth - 20) + "px";
            }
            if (previewContainer.getBoundingClientRect().bottom > window.innerHeight) {
                previewContainer.style.top = (window.innerHeight - previewContainer.offsetHeight - 20) + "px";
            }
        });
        previewContainer.style.display = "block";
        setTimeout(function () {
            previewContainer.style.opacity = 1;
        }, 0);
    }
})();