Aniworld Next Episode Button (Fixed v2)

Adds a Netflix-style "Next Episode" button to Aniworld

目前為 2025-03-01 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Aniworld Next Episode Button (Fixed v2)
// @namespace    http://tampermonkey.net/
// @version      0.3
// @description  Adds a Netflix-style "Next Episode" button to Aniworld
// @author       You
// @match        https://aniworld.to/anime/stream/*
// @license MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Configuration
    const BUTTON_TEXT = "Next Episode";
    const BUTTON_STYLE = `
        position: fixed;
        bottom: 20px;
        right: 20px;
        padding: 10px 20px;
        background-color: #040720;
        color: white;
        border: none;
        border-radius: 5px;
        font-weight: bold;
        cursor: pointer;
        z-index: 9999;
        opacity: 0.9;
        transition: opacity 0.3s ease;
        box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
    `;

    // Function to add the Next Episode button
    function addNextEpisodeButton() {
        // Remove existing button if it exists
        const existingButton = document.getElementById('netflix-style-next-button');
        if (existingButton) {
            existingButton.remove();
        }

        // Create the button element
        const button = document.createElement('button');
        button.textContent = BUTTON_TEXT;
        button.style.cssText = BUTTON_STYLE;
        button.id = "netflix-style-next-button";

        // Hover effect
        button.addEventListener('mouseover', () => {
            button.style.opacity = '1';
        });
        button.addEventListener('mouseout', () => {
            button.style.opacity = '0.9';
        });

        // Add click event listener
        button.addEventListener('click', navigateToNextEpisode);

        // Add the button to the page
        document.body.appendChild(button);

        console.log("Added Next Episode button");
    }

    // Function to navigate to the next episode
    function navigateToNextEpisode() {
        console.log("Next Episode button clicked");

        // Instead of trying to determine the current episode in complex ways,
        // let's directly look at the episode numbers and find the one with different styling

        // First, get all the episode links from the episode section
        const episodeContainer = document.querySelector('.hosterSiteDirectNav') ||
                               document.querySelector('.episodenav');

        if (!episodeContainer) {
            console.error("Could not find episode container");
            return;
        }

        // Get all episode links
        const episodeLinks = Array.from(episodeContainer.querySelectorAll('a'));

        if (!episodeLinks || episodeLinks.length === 0) {
            console.error("Could not find episode links");
            return;
        }

        console.log("Found", episodeLinks.length, "episode links");

        // Find the currently active episode based on styling
        let activeLink = null;
        let activeIndex = -1;

        // Method 1: Look for any link with a blue/dark background color
        episodeLinks.forEach((link, index) => {
            const style = window.getComputedStyle(link);
            const bgColor = style.backgroundColor;

            // Check if background color is not transparent, white, or black
            if (bgColor !== 'transparent' &&
                bgColor !== 'rgba(0, 0, 0, 0)' &&
                bgColor !== 'rgb(255, 255, 255)' &&
                bgColor !== 'rgb(0, 0, 0)') {

                console.log("Found active episode with bg color:", bgColor, "at index:", index, "text:", link.textContent.trim());
                activeLink = link;
                activeIndex = index;
            }
        });

        // Method 2: If not found by background, check if any link has an 'active' class
        if (!activeLink) {
            episodeLinks.forEach((link, index) => {
                if (link.classList.contains('active') ||
                    link.parentElement.classList.contains('active')) {

                    console.log("Found active episode with active class at index:", index, "text:", link.textContent.trim());
                    activeLink = link;
                    activeIndex = index;
                }
            });
        }

        // Method 3: Direct comparison of the parent containers
        if (!activeLink) {
            // Get all number containers (parent elements of links)
            const episodeNumbers = Array.from(episodeContainer.children);

            episodeNumbers.forEach((numContainer, index) => {
                const link = numContainer.querySelector('a');
                if (!link) return;

                const style = window.getComputedStyle(numContainer);
                const bgColor = style.backgroundColor;

                if (bgColor !== 'transparent' &&
                    bgColor !== 'rgba(0, 0, 0, 0)' &&
                    bgColor !== 'rgb(255, 255, 255)' &&
                    bgColor !== 'rgb(0, 0, 0)') {

                    console.log("Found active container with bg color:", bgColor, "at index:", index, "text:", link.textContent.trim());
                    activeLink = link;
                    activeIndex = index;
                }
            });
        }

        // If still not found, try one more approach - find a link with text color different from others
        if (!activeLink) {
            const firstLinkColor = window.getComputedStyle(episodeLinks[0]).color;

            episodeLinks.forEach((link, index) => {
                const style = window.getComputedStyle(link);
                if (style.color !== firstLinkColor) {
                    console.log("Found active episode with different text color at index:", index, "text:", link.textContent.trim());
                    activeLink = link;
                    activeIndex = index;
                }
            });
        }

        // If still not found, try to directly get the current episode number from the URL
        if (!activeLink) {
            // Try to extract episode number from URL
            const urlMatch = window.location.href.match(/\/episode-(\d+)/);
            if (urlMatch && urlMatch[1]) {
                const currentEpisodeNum = parseInt(urlMatch[1], 10);
                console.log("Extracted episode number from URL:", currentEpisodeNum);

                // Find the link with this number
                episodeLinks.forEach((link, index) => {
                    if (link.textContent.trim() === currentEpisodeNum.toString()) {
                        console.log("Found active episode from URL at index:", index);
                        activeLink = link;
                        activeIndex = index;
                    }
                });
            }
        }

        // Finally, if we still haven't found an active link, just use the first link as a fallback
        if (!activeLink && episodeLinks.length > 0) {
            // Last resort: Just try to directly click the next number in sequence
            // Get all the episode numbers
            const episodeNumbers = episodeLinks.map(link => {
                const num = parseInt(link.textContent.trim(), 10);
                return isNaN(num) ? 0 : num;
            });

            // Find the highest visible episode number on the page
            const currentEpisodeNum = Math.max(...episodeNumbers.filter(num => num > 0));
            console.log("Current highest episode number found:", currentEpisodeNum);

            // Try to click the next episode number
            const nextEpisodeNum = currentEpisodeNum + 1;

            // Find link with the next episode number
            let nextLink = null;
            episodeLinks.forEach(link => {
                if (link.textContent.trim() === nextEpisodeNum.toString()) {
                    nextLink = link;
                }
            });

            if (nextLink) {
                console.log("Directly clicking next episode number:", nextEpisodeNum);
                nextLink.click();
                return;
            } else {
                console.error("Could not find next episode link");
                return;
            }
        }

        // Now that we have the active episode, get the next one
        if (activeLink && activeIndex !== -1 && activeIndex < episodeLinks.length - 1) {
            const nextEpisodeLink = episodeLinks[activeIndex + 1];
            console.log("Next episode found at index:", activeIndex + 1, "text:", nextEpisodeLink.textContent.trim());

            // Click the next episode link
            nextEpisodeLink.click();
        } else if (activeLink) {
            // We're at the last episode of this season, try to go to next season
            const seasonContainer = document.querySelector('.staffelWrapperLoop') ||
                                   document.querySelector('.seasonNav');

            if (seasonContainer) {
                const seasonLinks = Array.from(seasonContainer.querySelectorAll('a'));
                let activeSeasonLink = null;
                let activeSeasonIndex = -1;

                // Find active season
                seasonLinks.forEach((link, index) => {
                    const style = window.getComputedStyle(link);
                    const bgColor = style.backgroundColor;

                    if (bgColor !== 'transparent' &&
                        bgColor !== 'rgba(0, 0, 0, 0)' &&
                        bgColor !== 'rgb(255, 255, 255)' &&
                        bgColor !== 'rgb(0, 0, 0)') {

                        activeSeasonLink = link;
                        activeSeasonIndex = index;
                    }
                });

                if (activeSeasonLink && activeSeasonIndex < seasonLinks.length - 1) {
                    // Go to next season, first episode
                    const nextSeasonLink = seasonLinks[activeSeasonIndex + 1];
                    console.log("Going to next season");
                    nextSeasonLink.click();
                } else {
                    alert("You've reached the last episode of the final season!");
                }
            } else {
                // Directly try to find episode by number
                const currentEpisodeText = activeLink.textContent.trim();
                const currentEpisodeNum = parseInt(currentEpisodeText, 10);

                if (!isNaN(currentEpisodeNum)) {
                    const nextEpisodeNum = currentEpisodeNum + 1;

                    // Try to find the next episode number in all links on the page
                    const allLinks = Array.from(document.querySelectorAll('a'));
                    const nextLink = allLinks.find(link => link.textContent.trim() === nextEpisodeNum.toString());

                    if (nextLink) {
                        console.log("Found next episode by number:", nextEpisodeNum);
                        nextLink.click();
                    } else {
                        alert("Could not find the next episode!");
                    }
                } else {
                    alert("Could not determine the next episode!");
                }
            }
        } else {
            alert("Could not determine the current episode!");
        }
    }

    // Create and add the button when the page is fully loaded
    function initializeButton() {
        setTimeout(addNextEpisodeButton, 1000);
    }

    // Wait for the page to fully load
    window.addEventListener('load', initializeButton);

    // Also run when page content changes (for single-page apps)
    const observer = new MutationObserver(function(mutations) {
        // If major DOM changes were detected, reinitialize the button
        let significantChanges = false;

        mutations.forEach(function(mutation) {
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                // Check if significant elements were added
                for (let i = 0; i < mutation.addedNodes.length; i++) {
                    const node = mutation.addedNodes[i];
                    if (node.nodeType === 1) { // Element node
                        if (node.classList &&
                            (node.classList.contains('hosterSiteDirectNav') ||
                             node.classList.contains('episodenav'))) {
                            significantChanges = true;
                            break;
                        }
                    }
                }
            }
        });

        if (significantChanges) {
            initializeButton();
        }
    });

    // Start observing
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // Initialize immediately in case page is already loaded
    if (document.readyState === 'complete' || document.readyState === 'interactive') {
        initializeButton();
    }
})();