anime365-ext-beta

Расширение для сайтов anime365 и smotret-anime

当前为 2025-05-24 提交的版本,查看 最新版本

// ==UserScript==
// @name         anime365-ext-beta
// @namespace    http://tampermonkey.net/
// @version      0.33-beta
// @description  Расширение для сайтов anime365 и smotret-anime
// @author       https://greasyfork.org/ru/users/1065796-kazaev
// @match        https://anime365.ru/catalog/*
// @match        https://anime-365.ru/catalog/*
// @match        https://smotret-anime.com/catalog/*
// @match        https://hentai365.ru/catalog/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=anime365.ru
// @license      MIT
// @grant        GM_addStyle
// ==/UserScript==

(function () {
    'use strict';

    class Logger {
        static settings = {
            console: true,
            ui: true,
            theatreMode: true,
            autoNextEpisode: {
                enabled: true,
                threshold: 99
            },
            autoplay: true,
        };

        static init() {
            if (this.settings.ui && !document.getElementById("anime365-log")) {
                const logDiv = document.createElement("div");
                logDiv.id = "anime365-log";
                logDiv.style.cssText = `
                    position: fixed;
                    bottom: 1rem;
                    right: 1rem;
                    background: rgba(0, 0, 0, 0.8);
                    color: #fff;
                    padding: 0.5rem 1rem;
                    font-size: 0.9rem;
                    border-radius: 0.5rem;
                    font-family: monospace;
                    z-index: 9999;
                    white-space: nowrap;
                `;
                document.body.appendChild(logDiv);
            }
        }

        static log(message, type = 'info') {
            const colors = {
                info: "#00aaff",
                success: "#00cc66",
                warn: "#ffaa00",
                error: "#ff4444"
            };

            if (this.settings.console) {
                const color = colors[type] || "#00aaff";
                console.log(`%c[anime365-ext] ${message}`, `color: ${color}`);
            }

            if (this.settings.ui) {
                const logEl = document.getElementById("anime365-log");
                if (!logEl) return;
                logEl.style.borderLeft = `4px solid ${colors[type] || "#00aaff"}`;
                logEl.textContent = `[anime365-ext] ${message}`;
            }
        }
    }

    class TheatreMode {
        constructor() {
            this.styleRef = null;
            this.closeBtn = this.createCloseButton();
            this.active = false;
            this.init();
        }

        init() {
            const bg = document.querySelector(".full-background");
            if (bg) bg.after(this.closeBtn);

            document.addEventListener("keydown", (e) => {
                if (!Logger.settings.theatreMode) return;

                if (e.altKey && e.code === "KeyT") {
                    e.preventDefault();
                    this.toggle();
                } else if (e.key === "Escape") {
                    e.preventDefault();
                    this.close();
                }
            });

            this.closeBtn.addEventListener("click", () => this.close());

            // Автовоспроизведение при открытии, если включено в настройках
            if (Logger.settings.autoplay) {
                const autoplayInterval = setInterval(() => {
                    const doc = this.getIframeDocument();
                    const playBtn = doc?.querySelector(".vjs-big-play-button");
                    if (playBtn) {
                        playBtn.click();
                        Logger.log("Автовоспроизведение: нажата кнопка Play", 'success');
                        clearInterval(autoplayInterval);
                    }
                }, 500);
            }
        }

        getIframeDocument() {
            return document.querySelector("iframe")?.contentDocument || null;
        }

        toggle() {
            if (!Logger.settings.theatreMode) return;

            const container = this.getVideoContainer();
            if (!container) return;
            container.classList.contains("cinema-mode") ? this.close() : this.open();
        }

        open() {
            if (!Logger.settings.theatreMode) return;

            const container = this.getVideoContainer();
            if (!container) return;

            container.classList.add("cinema-mode");
            this.styleRef = GM_addStyle(`
                .m-translation-player > .card-image > .video-container.cinema-mode {
                    position: fixed !important;
                    z-index: 10;
                    height: 100vh;
                    width: 100vw;
                    left: 0;
                    top: 0;
                    margin: 0;
                    padding: 0;
                }
                body {
                    overflow: hidden !important;
                }
            `);
            this.closeBtn.style.display = "block";
            this.active = true;
            Logger.log("Включён кинотеатр", 'success');
        }

        close() {
            if (!this.active) return;

            const container = this.getVideoContainer();
            if (!container) return;

            container.classList.remove("cinema-mode");
            this.closeBtn.style.display = "none";

            if (this.styleRef) {
                document.getElementById(this.styleRef.id)?.remove();
                this.styleRef = null;
            }

            this.active = false;
            Logger.log("Кинотеатр выключен", 'warn');
        }

        getVideoContainer() {
            return document.querySelector(".m-translation-player > .card-image > .video-container");
        }

        createCloseButton() {
            const btn = document.createElement("img");
            btn.id = "cinemaCloseButton";
            btn.style.cssText = "z-index: 20; position: fixed; top: 0; right: 0; margin: 1rem; width: 2rem; display: none; cursor: pointer;";
            btn.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none'%3E%3Cpath d='M16 8L8 16M12 12L16 16M8 8L10 10M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z' stroke='%23fff' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E";
            return btn;
        }
    }

    class AutoNextEpisode {
        constructor() {
            this.lastProgress = null;
            this.lastLoggedProgress = null;
            this.interval = setInterval(() => this.checkProgress(), 1000);
        }

        getIframeDocument() {
            return document.querySelector("iframe")?.contentDocument || null;
        }

        checkProgress() {
            if (!Logger.settings.autoNextEpisode.enabled) return;

            const doc = this.getIframeDocument();
            if (!doc) return;

            const playProgressEl = doc.querySelector(".vjs-play-progress");
            if (!playProgressEl) return;

            // Получаем progress в процентах (с десятичной точностью)
            const widthStyle = playProgressEl.style.width;
            if (!widthStyle.endsWith("%")) return;

            const progress = parseFloat(widthStyle);
            if (isNaN(progress)) return;

            const video = doc.querySelector("video");
            if (!video) return;

            // Логируем только если видео играет (не на паузе)
            if (!video.paused) {
                const progressFormatted = progress.toFixed(1);
                if (progressFormatted !== this.lastLoggedProgress) {
                    Logger.log(`Прогресс видео: ${progressFormatted}%`, 'info');
                    this.lastLoggedProgress = progressFormatted;
                }
            }

            // Если прогресс >= заданного порога - переходим к следующей серии
            if (progress >= Logger.settings.autoNextEpisode.threshold) {
                Logger.log(`Видео достигло ${progress.toFixed(1)}%. Переход к следующей серии...`, 'success');
                this.goToNextEpisode();
                // Не останавливаем интервал, чтобы скрипт продолжал работать после перехода
                return;
            }

            // Если прогресс застывает на 0% и кнопка play доступна - повторно запускаем видео
            if (this.lastProgress === 0 && progress === 0 && doc.querySelector(".vjs-big-play-button")) {
                doc.querySelector(".vjs-big-play-button").click();
                Logger.log("Прогресс 0%. Повторный запуск плеера", 'warn');
            }

            this.lastProgress = progress;
        }

        goToNextEpisode() {
            // Кликаем по ссылке, а не по иконке
            const nextLink = document.querySelector(".m-select-sibling-episode > a:nth-child(2)");
            if (nextLink) {
                Logger.log("Переходим к следующей серии", 'info');
                nextLink.click();
                // Если переход не срабатывает через клик, можно раскомментировать:
                // window.location.href = nextLink.href;
            } else {
                Logger.log("Кнопка перехода к следующей серии не найдена", 'error');
            }
        }
    }

    function onDocumentReady(callback) {
        document.addEventListener('page:load', callback);
        document.addEventListener('turbolinks:load', callback);
        if (document.readyState !== 'loading') {
            callback();
        } else {
            document.addEventListener('DOMContentLoaded', callback);
        }
    }

    onDocumentReady(() => {
        // Настройки
        Logger.settings.console = true; // Логирование в консоль
        Logger.settings.ui = true; // Логирование в UI

        Logger.settings.theatreMode = true; // Включение режима кинотеатра

        Logger.settings.autoNextEpisode = {
            enabled: true, // Включить авто-переход к следующей серии
            threshold: 97 // Процент прогресса для перехода (по умолчанию 97)
        };
        Logger.settings.autoplay = true; // Включить автозапуск видео

        Logger.init();
        Logger.log("Инициализация расширения...", 'info');

        if (Logger.settings.theatreMode) {
            new TheatreMode();
        }

        new AutoNextEpisode();
    });

})();