erai-raws preview Image

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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);
})();