erai-raws preview Image

Show image preview next to the anime titles by hovering the mouse.

// ==UserScript==
// @name         erai-raws preview Image
// @namespace    http://tampermonkey.net/
// @version      2.5
// @description  Show image preview next to the anime titles by hovering the mouse.
// @author       dr.bobo0
// @match        https://www.erai-raws.info/
// @match        https://www.erai-raws.info/anime-list/
// @match        https://www.erai-raws.info/latest-releases/*
// @match        https://www.erai-raws.info/subtitles/*
// @match        https://www.erai-raws.info/batches/*
// @match        https://www.erai-raws.info/specials/*
// @match        https://www.erai-raws.info/encodes/*
// @match        https://www.erai-raws.info/release-schedule/
// @match        https://www.erai-raws.info/release-schedule-v2/
// @match        https://www.erai-raws.info/my-anime-list/
// @match        https://www.erai-raws.info/episodes/*
// @icon         data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAMFBMVEVHcEy3ISG3ISG3ISG3ISG3ISG3ISG3ISG3ISG3ISG3ISG3ISG3ISG3ISG3ISG3ISFbcynrAAAAD3RSTlMA1OaywnKHOhgNXPQmnEsN6GgJAAACVUlEQVRYhe1WSYKEIAyUfZf//3YgLAbEbpzrTJ1EKbJVIsfxj78Ck/BLqj4lI9TF6Ci3r8mW0YjBX7EliTeIbfo5sAkPQQaWHjbp6qKTYH07NEa6x5eNzawuB6YzfHIghi1+qBmztXI21UBleqRbtVRAD7qtxRXOnhZ49h1tbXS6K4NUejmtc0DnJv04UgU8XvvAgtVPuxcQ2dvfKh/AS/2F8otjjLJCJlh//9bRdZBbiBLCawBKcoc+fdI1OiJD5sbit854ELa2gbpx57lgx7UwtRh70FHCGcdNTa3y2mh/ZsHOiTiRIcrkCVkUzZ3i1xW5mgRz2G7IsavwtTNc8JAYhghkWPUWdkHhY8EssTWxw0wiOI0t72RWfIqKqWPF97gONfiF4o2ATIkbP2u+vyh8oo4nnPe6B5TR4v8HZak7HzhV6xqK9KHhPGwYXjFsUqxl6V0NUedKOGxA0aGkKQPu3nhnc9HAbiQ6mK9YEmQV/xUjCOTKb5mvQyPMMyhb5d0GPLXyatHkivXCsAFAmcQQF9gjUsB/toudmWk7zqGpVqAu02So9Mlhk3cLaD3je+uz7kpgmM3FveD13+HQIKEQta0CpuUzYUKtx+3saJ13IMD8P8sldUtmw/A7b1cRUGgEh8dKLuFF4GkApznUX9GLlrMgn6hP4Kjcdq32j4BZ1qxCNO/4YpQ7/Z6EEaUA1/ptElABLofeJIHMdVPvkgAJHFrc4J78CjUmsPvE1tvvoPF+I5OlHbauKmol3JLWuHWAnxNQ3jJKw+ZdybLXN/t//HX8ACejMFAROu21AAAAAElFTkSuQmCC
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Method 1: Block images with the .preview-image class using CSS
    const style = document.createElement('style');
    style.textContent = `
        .preview-image {
            display: none !important;
        }
    `;
    document.head.appendChild(style);

    // Part 1: Notification Popup

    function showPopup() {
        // Create the dark overlay
        const overlay = document.createElement('div');
        overlay.style.position = 'fixed';
        overlay.style.top = '0';
        overlay.style.left = '0';
        overlay.style.width = '100%';
        overlay.style.height = '100%';
        overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.85)';
        overlay.style.zIndex = '9998';
        overlay.style.backdropFilter = 'blur(5px)';

        // Create the pop-up container
        const popup = document.createElement('div');
        popup.style.position = 'fixed';
        popup.style.top = '50%';
        popup.style.left = '50%';
        popup.style.transform = 'translate(-50%, -50%)';
        popup.style.width = '600px';
        popup.style.padding = '30px';
        popup.style.backgroundColor = '#1a1a1a';
        popup.style.color = '#ffffff';
        popup.style.border = '1px solid #333';
        popup.style.borderRadius = '15px';
        popup.style.boxShadow = '0 0 30px rgba(0,0,0,0.7)';
        popup.style.zIndex = '9999';
        popup.style.textAlign = 'center';

        // Add an image to the pop-up
        const img = document.createElement('img');
        img.src = 'https://i.imgur.com/ItdkLgt.png';
        img.style.width = '100%';
        img.style.height = 'auto';
        img.style.marginBottom = '30px';
        img.style.borderRadius = '10px';
        popup.appendChild(img);

        // Add the anime title and message
        const title = document.createElement('p');
        title.textContent = 'Enhance Erai-raws with Image Previews!';
        title.style.marginBottom = '15px';
        title.style.fontSize = '28px';
        title.style.fontWeight = 'bold';
        title.style.color = '#ff9900';
        popup.appendChild(title);

        const message = document.createElement('p');
        message.textContent = "Hey! I've created a new script. Take a look ☝️, and if you like it, feel free to install it. Thank you so much for all your support!  ⚠️ Also, preview image has now been officially added to the site. You can use the official one, but if you prefer this one , you can still use the current one. However, doing so will disable the native preview image on the site.";
        message.style.marginBottom = '25px';
        message.style.fontSize = '18px';
        message.style.lineHeight = '1.6';
        message.style.color = '#cccccc';
        popup.appendChild(message);

        // Add a link to the new script
        const link = document.createElement('a');
        link.href = 'https://greasyfork.org/en/scripts/506193-erai-raws-image-previews-next-to-anime-titles';
        link.textContent = 'Install New Script';
        link.style.display = 'inline-block';
        link.style.color = '#4CAF50';
        link.style.textDecoration = 'none';
        link.style.marginBottom = '25px';
        link.style.fontSize = '20px';
        link.style.fontWeight = 'bold';
        link.style.padding = '10px 20px';
        link.style.border = '2px solid #4CAF50';
        link.style.borderRadius = '5px';
        link.style.transition = 'all 0.3s ease';
        link.addEventListener('mouseover', function() {
            this.style.backgroundColor = '#4CAF50';
            this.style.color = '#ffffff';
        });
        link.addEventListener('mouseout', function() {
            this.style.backgroundColor = 'transparent';
            this.style.color = '#4CAF50';
        });
        popup.appendChild(link);

        // Add a checkbox to never show the popup again
        const checkboxLabel = document.createElement('label');
        checkboxLabel.style.display = 'block';
        checkboxLabel.style.marginBottom = '20px';
        checkboxLabel.style.fontSize = '16px';
        checkboxLabel.style.cursor = 'pointer';
        checkboxLabel.style.color = '#999999';
        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.style.marginRight = '10px';
        checkbox.style.transform = 'scale(1.5)';
        checkboxLabel.appendChild(checkbox);
        checkboxLabel.appendChild(document.createTextNode('Never show this again'));
        popup.appendChild(checkboxLabel);

        // Add a dismiss button
        const dismissButton = document.createElement('button');
        dismissButton.textContent = 'Dismiss';
        dismissButton.style.padding = '12px 24px';
        dismissButton.style.backgroundColor = '#333333';
        dismissButton.style.color = '#ffffff';
        dismissButton.style.border = 'none';
        dismissButton.style.borderRadius = '5px';
        dismissButton.style.cursor = 'pointer';
        dismissButton.style.fontSize = '18px';
        dismissButton.style.transition = 'all 0.3s ease';
        dismissButton.addEventListener('mouseover', function() {
            this.style.backgroundColor = '#555555';
        });
        dismissButton.addEventListener('mouseout', function() {
            this.style.backgroundColor = '#333333';
        });
        dismissButton.addEventListener('click', function() {
            if (checkbox.checked) {
                localStorage.setItem('popupNeverShow', 'true');
                notificationButton.remove();
            } else {
                // Change the notification button background color to gray
                notificationButton.style.backgroundColor = '#333333';
                localStorage.setItem('popupButtonColor', '#333333');
            }
            overlay.remove();
            popup.remove();
        });
        popup.appendChild(dismissButton);

        // Append the overlay and pop-up to the body
        document.body.appendChild(overlay);
        document.body.appendChild(popup);
    }

    // Create the notification button
    const notificationButton = document.createElement('button');
    notificationButton.textContent = '🔔 New!';
    notificationButton.style.position = 'fixed';
    notificationButton.style.bottom = '20px';
    notificationButton.style.right = '20px';
    notificationButton.style.padding = '10px 20px';
    notificationButton.style.backgroundColor = localStorage.getItem('popupButtonColor') || '#ff9900';
    notificationButton.style.color = '#ffffff';
    notificationButton.style.border = 'none';
    notificationButton.style.borderRadius = '5px';
    notificationButton.style.cursor = 'pointer';
    notificationButton.style.fontSize = '16px';
    notificationButton.style.fontWeight = 'bold';
    notificationButton.style.boxShadow = '0 2px 10px rgba(0,0,0,0.2)';
    notificationButton.style.zIndex = '9997';

    // Add click event to the notification button
    notificationButton.addEventListener('click', showPopup);

    // Function to initialize the script
    function init() {
        if (!localStorage.getItem('popupNeverShow')) {
            document.body.appendChild(notificationButton);
        }

        // Part 2: Image Preview

        const storagePrefix = "erai_raws_image_cache_0";

        // Function to clear old localStorage keys
        function clearOldCache() {
            for (let i = 0; i < localStorage.length; i++) {
                const key = localStorage.key(i);
                if (key && !key.startsWith(storagePrefix)) {
                    localStorage.removeItem(key);
                }
            }
        }

        // Clear old cache on script initialization
        clearOldCache();

        document.querySelectorAll("th > a.aa_ss_ops_new").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);

               const url = this.href;
               const localStorageKey = storagePrefix + url;
               const tableRow = this.closest('tr');
               const previewImageUrl = tableRow ? tableRow.getAttribute('data-preview-image') : null;

               if (previewImageUrl) {
                    const cachedImage = localStorage.getItem(storagePrefix + previewImageUrl);
                   if (cachedImage) {
                       displayCachedImage(cachedImage,previewImageUrl);
                    } else {
                        showLoadingIndicator();
                        fetchAndDisplayImage(previewImageUrl, localStorageKey, true);
                   }

               } else {
                   const cachedImage = localStorage.getItem(localStorageKey);

                   if (cachedImage) {
                       displayCachedImage(cachedImage, url);
                   } else {
                       showLoadingIndicator();
                       fetchAndDisplayImage(url, localStorageKey, false);
                   }
               }

               function displayCachedImage(imageSrc,url) {
                   let cachedImageElement = new Image();
                   cachedImageElement.onload = function() {
                       previewContainer.innerHTML = `<img style="width: 100%; height: 100%;" src="${imageSrc}"/>`;
                       showPreviewContainer();
                   };
                   cachedImageElement.onerror = function() {
                       localStorage.removeItem(storagePrefix+url);
                       showLoadingIndicator();
                       fetchAndDisplayImage(url, localStorageKey, previewImageUrl);
                   };
                   cachedImageElement.src = imageSrc;
               }

                function showLoadingIndicator() {
                    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>
                    `;
                    showPreviewContainer();
                }

                function showPreviewContainer() {
                    previewContainer.style.display = "block";
                    setTimeout(function() {
                        previewContainer.style.opacity = 1;
                    }, 0);
                }

                function fetchAndDisplayImage(url, localStorageKey, useTableRowImage) {
                    if(useTableRowImage){
                        let image = new Image();
                        image.onload = function() {
                            try {
                                localStorage.setItem(storagePrefix+url, url);
                            } catch (e) {
                                console.warn("LocalStorage is full or unavailable. Image caching failed.", e);
                            }
                            previewContainer.innerHTML = `<img style="width: 100%; height: 100%;" src="${url}"/>`;
                             showPreviewContainer();
                         };
                        image.onerror = function() {
                             previewContainer.textContent = "Failed to load image.";
                        };
                       image.src = url;
                    } else {
                        var xhr = new XMLHttpRequest();
                        xhr.open("GET", url);
                        xhr.responseType = "document";

                        xhr.onload = function() {
                            if (xhr.response) {
                                let preview = xhr.response.querySelector(".entry-content-poster img");
                                if (preview) {
                                    // Use data-src if present, fallback to src
                                    const imageUrl = preview.getAttribute('data-src') || preview.src;

                                    let image = new Image();
                                    image.onload = function() {
                                        try {
                                            localStorage.setItem(localStorageKey, imageUrl);
                                        } catch (e) {
                                            console.warn("LocalStorage is full or unavailable. Image caching failed.", e);
                                        }
                                        previewContainer.innerHTML = `<img style="width: 100%; height: 100%;" src="${imageUrl}"/>`;
                                        showPreviewContainer();
                                    };
                                    image.onerror = function() {
                                        previewContainer.textContent = "Failed to load image.";
                                    };
                                    image.src = imageUrl;
                                } else {
                                    previewContainer.textContent = "Image not found.";
                                }
                            } else {
                                previewContainer.textContent = "Failed to load image.";
                            }
                        };

                        xhr.onerror = function() {
                            previewContainer.textContent = "Failed to load image.";
                        };

                        xhr.send();
                    }
                }

                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";
                     }
                });

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

    // Run the initialization function when the page loads
    window.addEventListener('load', init);
})();