YouTube Split

Устанавливает сплит по времени, панель управления под видео, показывает статистику "Выкуплено/Всего минут", оверлей "СПЛИТ НЕ ОПЛАЧЕН" с гифкой и проигрывает звук при достижении порога.

当前为 2025-04-18 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         YouTube Split
// @namespace    http://tampermonkey.net/
// @version      2.2
// @author       Gemini 2.5 Flash
// @match        *://www.youtube.com/watch*
// @grant        none
// @description Устанавливает сплит по времени, панель управления под видео, показывает статистику "Выкуплено/Всего минут", оверлей "СПЛИТ НЕ ОПЛАЧЕН" с гифкой и проигрывает звук при достижении порога.
// ==/UserScript==

(function() {
    'use strict';

    // --- Конфигурация ---
    let splitMinutes = null;
    let totalVideoMinutes = null;
    const extendCost = 300;
    const splitSoundUrl = 'https://github.com/lardan099/donat/raw/refs/heads/main/alert_orig.mp3'; // ВСТАВЬТЕ СЮДА ПРЯМУЮ ССЫЛКУ НА ВАШ ЗВУК!
    const overlayGifUrl = 'https://i.imgur.com/SS5Nfff.gif'; // URL гифки для оверлея
    const localStorageVolumeKey = 'ytSplitAlertVolume';


    // --- Глобальные переменные состояния ---
    let video = null;
    let overlay = null;
    let splitTriggered = false;
    let audioPlayer = null;
    let splitCheckIntervalId = null;
    let setupIntervalId = null;
    let panelAdded = false;


    const styles = `
        #split-control-panel {
            margin-top: 10px;
            margin-bottom: 15px;
            padding: 10px 15px;
            background: var(--yt-spec-badge-chip-background);
            border: 1px solid var(--yt-spec-border-div);
            border-radius: 8px;
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            gap: 10px 20px;
            color: var(--yt-spec-text-primary);
            font-family: "Roboto", Arial, sans-serif;
            font-size: 14px;
            max-width: var(--ytd-watch-flexy-width);
            width: 100%;
            box-sizing: border-box;
        }

        ytd-watch-flexy:not([use- Sarkis]) #primary #split-control-panel {
            margin-left: auto;
            margin-right: auto;
        }

        #split-control-panel label {
            font-weight: 500;
            color: var(--yt-spec-text-secondary);
            flex-shrink: 0;
            line-height: 1.3;
        }

        #split-control-panel label i {
             font-style: normal;
             font-size: 12px;
             color: var(--yt-spec-text-disabled);
        }

        #split-input-group {
            display: flex;
            align-items: center;
            gap: 5px;
        }

        #split-control-panel input[type="number"] {
            width: 60px;
            padding: 8px 10px;
            background: var(--yt-spec-filled-button-background);
            color: var(--yt-spec-text-primary);
            border: 1px solid var(--yt-spec-action-simulate-border);
            border-radius: 4px;
            text-align: center;
            font-size: 15px;
            -moz-appearance: textfield;
        }

        #split-control-panel input[type="number"]::-webkit-outer-spin-button,
        #split-control-panel input[type="number"]::-webkit-inner-spin-button {
            -webkit-appearance: none;
            margin: 0;
        }

        #split-control-panel button {
            padding: 8px 15px;
            font-size: 15px;
            cursor: pointer;
            background: var(--yt-spec-grey-1);
            color: var(--yt-spec-text-primary);
            border: none;
            border-radius: 4px;
            transition: background 0.2s ease-in-out;
            font-weight: 500;
            flex-shrink: 0;
        }

        #split-control-panel button:hover {
             background: var(--yt-spec-grey-2);
        }

        #split-input-group button {
            padding: 8px 10px;
            font-size: 14px;
            background: var(--yt-spec-filled-button-background);
            border: 1px solid var(--yt-spec-action-simulate-border);
        }
         #split-input-group button:hover {
            background: var(--yt-spec-grey-2);
         }

        #split-control-panel button#set-split-button {
             background: var(--yt-spec-brand-suggested-action);
             color: var(--yt-spec-text-reverse);
             order: -1;
             margin-right: auto;
        }
         #split-control-panel button#set-split-button:hover {
             background: var(--yt-spec-brand-suggested-action-hover);
        }

        #split-volume-control {
             display: flex;
             align-items: center;
             gap: 5px;
        }
         #split-volume-control label {
             font-weight: 500;
             color: var(--yt-spec-text-secondary);
             flex-shrink: 0;
             line-height: normal;
         }
         #split-volume-control input[type="range"] {
             flex-grow: 1;
             min-width: 80px;
             -webkit-appearance: none;
             appearance: none;
             height: 8px;
             background: var(--yt-spec-grey-1);
             outline: none;
             opacity: 0.7;
             transition: opacity .2s;
             border-radius: 4px;
         }
         #split-volume-control input[type="range"]:hover {
             opacity: 1;
         }

        #split-volume-control input[type="range"]::-webkit-slider-thumb {
             -webkit-appearance: none;
             appearance: none;
             width: 15px;
             height: 15px;
             background: var(--yt-spec-brand-button-background);
             cursor: pointer;
             border-radius: 50%;
        }

        #split-volume-control input[type="range"]::-moz-range-thumb {
             width: 15px;
             height: 15px;
             background: var(--yt-spec-brand-button-background);
             cursor: pointer;
             border-radius: 50%;
        }

        #split-stats {
            font-size: 15px;
            color: var(--yt-spec-text-primary);
            font-weight: 500;
            margin-left: 10px;
        }


        #split-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(0, 0, 0, 0.95);
            color: white;
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            z-index: 99999;
            font-family: "Roboto", Arial, sans-serif;
            text-align: center;
            padding: 20px;
            box-sizing: border-box;
        }

        #split-overlay #split-warning-message {
            font-size: clamp(24px, 4vw, 36px);
            margin-bottom: 15px;
            color: yellow;
            font-weight: bold;
            text-shadow: 0 0 8px rgba(255, 255, 0, 0.5);
        }

        #split-overlay #split-main-message {
            font-size: clamp(40px, 8vw, 72px);
            font-weight: bold;
            margin-bottom: 40px;
            color: red;
            text-shadow: 0 0 15px rgba(255, 0, 0, 0.7);
        }

        #split-extend-buttons {
            display: flex;
            gap: 15px;
            flex-wrap: wrap;
            justify-content: center;
            margin-bottom: 40px; /* Отступ перед гифкой */
        }

        #split-extend-buttons button {
            padding: 12px 25px;
            font-size: clamp(18px, 3vw, 24px);
            cursor: pointer;
            background: var(--yt-spec-red-500);
            border: none;
            color: white;
            border-radius: 4px;
            font-weight: bold;
            transition: background 0.2s ease-in-out;
        }

        #split-extend-buttons button:hover {
            background: var(--yt-spec-red-600);
        }

        /* Стили для гифки на оверлее */
        #split-overlay img {
            max-width: 115%; /* Увеличим максимальную ширину */
            max-height: 50vh; /* Увеличим максимальную высоту (например, до 50% высоты вьюпорта) */
            height: auto;
            border-radius: 8px;
            margin-top: 20px; /* Добавим отступ сверху, чтобы отделить от кнопок */
        }
    `;

    function injectStyles() {
        if (document.getElementById('yt-split-styles')) {
            return;
        }
        const styleElement = document.createElement("style");
        styleElement.id = 'yt-split-styles';
        styleElement.textContent = styles;
        document.head.appendChild(styleElement);
    }

    function updateSplitDisplay() {
        const inputField = document.getElementById("split-input");
        if (inputField) {
            inputField.valueAsNumber = splitMinutes === null ? 0 : splitMinutes;
        }
        updateSplitStatsDisplay();
    }

    function updateSplitStatsDisplay() {
        const statsElement = document.getElementById("split-stats");
        if (statsElement) {
             const boughtMinutes = splitMinutes === null ? 0 : splitMinutes;
             const totalMinutesText = totalVideoMinutes !== null ? `${totalVideoMinutes}` : '?';
             statsElement.textContent = `Выкуплено: ${boughtMinutes} / Всего: ${totalMinutesText} минут`;
        }
    }

     function modifySplitInput(minutesToModify) {
         const inputField = document.getElementById("split-input");
         if (!inputField) return;

         let currentVal = inputField.valueAsNumber;

         if (isNaN(currentVal)) {
             currentVal = 0;
         }

         let newVal = currentVal + minutesToModify;

         if (newVal < 0) {
             newVal = 0;
         }

         inputField.valueAsNumber = newVal;
     }

    function startSplitCheckInterval() {
        if (!splitCheckIntervalId) {
            splitCheckIntervalId = setInterval(checkSplitCondition, 500);
        }
    }

    function stopSplitCheckInterval() {
        if (splitCheckIntervalId) {
            clearInterval(splitCheckIntervalId);
            splitCheckIntervalId = null;
        }
    }

    function addControlPanel(primaryContainer) {
        if (panelAdded) {
            ensurePanelPosition();
            updateSplitDisplay();
            return;
        }

        if (!primaryContainer) {
             return;
        }

        const panel = document.createElement("div");
        panel.id = "split-control-panel";

        const setButton = document.createElement("button");
        setButton.id = "set-split-button";
        setButton.textContent = "НАЧАТЬ СПЛИТ";
        setButton.addEventListener("click", function() {
            const inputField = document.getElementById("split-input");
            const inputVal = inputField.valueAsNumber;

            if (!isNaN(inputVal) && inputVal >= 0) {
                const oldSplitMinutes = splitMinutes;
                splitMinutes = inputVal;

                 if (splitMinutes > 0) {
                     startSplitCheckInterval();
                     setButton.textContent = "СПЛИТ НАЧАТ";
                     setButton.style.background = 'var(--yt-spec-call-to-action)';

                     if (video) {
                         const thresholdSeconds = splitMinutes * 60;
                         if (video.currentTime >= thresholdSeconds) {
                             video.pause();
                             splitTriggered = true;
                             showOverlay();
                             if(splitSoundUrl && audioPlayer && audioPlayer.src !== 'ВАША_ПРЯМАЯ_ССЫЛКА_НА_ЗВУКОВОЙ_ФАЙЛ_ТУТ'){
                                 audioPlayer.pause();
                                 audioPlayer.currentTime = 0;
                                 audioPlayer.play().catch(e => console.error("YouTube Split: Ошибка при воспроизведении звука:", e));
                             }
                         } else {
                             splitTriggered = false;
                             removeOverlay();
                             if (video.paused && oldSplitMinutes === null) {
                                 video.play();
                             }
                         }
                     }

                 } else { // splitMinutes === 0
                     stopSplitCheckInterval();
                     splitTriggered = false;
                     removeOverlay();
                     setButton.textContent = "НАЧАТЬ СПЛИТ";
                     setButton.style.background = 'var(--yt-spec-brand-suggested-action)';

                     if (video && video.paused && oldSplitMinutes !== null && oldSplitMinutes > 0) {
                         video.play();
                     }
                 }
                 updateSplitDisplay();
                 updateSplitStatsDisplay();
            } else {
                alert("Введите корректное число минут.");
            }
        });

        const label = document.createElement("label");
        label.setAttribute("for", "split-input");
        const labelTextMain = document.createTextNode("Сплит (мин):");
        const breakElement = document.createElement("br");
        const italicElement = document.createElement("i");
        const labelTextInstruction = document.createTextNode("(уст. перед \"Начать\")");

        label.appendChild(labelTextMain);
        label.appendChild(breakElement);
        italicElement.appendChild(labelTextInstruction);
        label.appendChild(italicElement);

        const inputGroup = document.createElement("div");
        inputGroup.id = "split-input-group";

        const inputField = document.createElement("input");
        inputField.type = "number";
        inputField.id = "split-input";
        inputField.min = "0";
        inputField.valueAsNumber = splitMinutes === null ? 0 : splitMinutes;

         const modifyButtons = [
             { text: '-10', minutes: -10 },
             { text: '-5', minutes: -5 },
             { text: '-1', minutes: -1 },
             { text: '+1', minutes: 1 },
             { text: '+5', minutes: 5 },
             { text: '+10', minutes: 10 },
             { text: '+20', minutes: 20 }
         ];

         modifyButtons.forEach(btnInfo => {
             const button = document.createElement("button");
             button.textContent = btnInfo.text;
             button.addEventListener("click", () => modifySplitInput(btnInfo.minutes));
             inputGroup.appendChild(button);
         });

        inputGroup.insertBefore(inputField, inputGroup.children[0]);


        const volumeControlGroup = document.createElement("div");
        volumeControlGroup.id = "split-volume-control";

        const volumeLabel = document.createElement("label");
        volumeLabel.setAttribute("for", "split-volume-slider");
        volumeLabel.textContent = "Громкость алерта:";

        const volumeSlider = document.createElement("input");
        volumeSlider.type = "range";
        volumeSlider.id = "split-volume-slider";
        volumeSlider.min = "0";
        volumeSlider.max = "1";
        volumeSlider.step = "0.05";

        let savedVolume = localStorage.getItem(localStorageVolumeKey);
        if (savedVolume === null) {
             savedVolume = '0.5';
        }
        volumeSlider.value = savedVolume;

        volumeSlider.addEventListener("input", function() {
             if (audioPlayer) {
                 audioPlayer.volume = parseFloat(this.value);
             }
             localStorage.setItem(localStorageVolumeKey, this.value);
        });

        volumeControlGroup.appendChild(volumeLabel);
        volumeControlGroup.appendChild(volumeSlider);

        if (audioPlayer) {
             audioPlayer.volume = parseFloat(savedVolume);
        }

        const statsElement = document.createElement("span");
        statsElement.id = "split-stats";


        panel.appendChild(setButton);
        panel.appendChild(label);
        panel.appendChild(inputGroup);
        panel.appendChild(volumeControlGroup);
        panel.appendChild(statsElement);

        primaryContainer.insertBefore(panel, primaryContainer.firstChild);
        panelAdded = true;
        updateSplitDisplay();
        updateSplitStatsDisplay();
    }

     function ensurePanelPosition() {
         if (!panelAdded) return;

         const panel = document.getElementById("split-control-panel");
         const primaryContainer = document.querySelector("ytd-watch-flexy #primary");

         if (panel && primaryContainer) {
             if (primaryContainer.firstChild !== panel) {
                  primaryContainer.insertBefore(panel, primaryContainer.firstChild);
             }
         }
     }


    function addMinutesToActiveSplit(minutesToAdd) {
        if (splitMinutes === null) return;

        splitMinutes += minutesToAdd;
        updateSplitDisplay();

        const thresholdSeconds = splitMinutes * 60;

        if (video && video.currentTime < thresholdSeconds) {
            removeOverlay();
            splitTriggered = false;
            video.play();
        }
    }

    function checkSplitCondition() {
        if (!video) {
            video = document.querySelector("video");
            if (!video) {
                 stopSplitCheckInterval();
                 splitTriggered = false;
                 removeOverlay();
                 return;
            }
             initAudioPlayer();
             const volumeSlider = document.getElementById('split-volume-slider');
             if(audioPlayer && volumeSlider) audioPlayer.volume = parseFloat(volumeSlider.value);
        }

        if (totalVideoMinutes === null && isFinite(video.duration) && video.duration > 0) {
             totalVideoMinutes = Math.ceil(video.duration / 60);
             if (panelAdded) {
                 updateSplitStatsDisplay();
             }
        }


        if (splitMinutes !== null && splitMinutes > 0) {
            const thresholdSeconds = splitMinutes * 60;

            if (video.currentTime >= thresholdSeconds && !splitTriggered) {
                video.pause();
                splitTriggered = true;
                showOverlay();
                if(splitSoundUrl && audioPlayer && audioPlayer.src !== 'ВАША_ПРЯМАЯ_ССЫЛКА_НА_ЗВУКОВОЙ_ФАЙЛ_ТУТ'){
                     audioPlayer.pause();
                     audioPlayer.currentTime = 0;
                     audioPlayer.play().catch(e => console.error("YouTube Split: Ошибка при воспроизведении звука:", e));
                }
            }
            if (splitTriggered && video.currentTime < thresholdSeconds) {
                removeOverlay();
                splitTriggered = false;
                video.play();
            }
        } else {
             stopSplitCheckInterval();
             splitTriggered = false;
             removeOverlay();
             if (video && video.paused) video.play();
        }
    }

    function showOverlay() {
        if (overlay) return;

        overlay = document.createElement("div");
        overlay.id = "split-overlay";

        const warningMessage = document.createElement("div");
        warningMessage.id = "split-warning-message";
        warningMessage.textContent = "⚠️ НУЖНО ДОНАТНОЕ ТОПЛИВО ⚠️";

        const splitMessage = document.createElement("div");
        splitMessage.id = "split-main-message";
        splitMessage.textContent = "СПЛИТ НЕ ОПЛАЧЕН";

        const extendButtonsContainer = document.createElement("div");
        extendButtonsContainer.id = "split-extend-buttons";

        const extendButtonConfigs = [
             { minutes: 1, cost: extendCost },
             { minutes: 5, cost: extendCost * 5 },
             { minutes: 10, cost: extendCost * 10 },
             { minutes: 20, cost: extendCost * 20 }
        ];

        extendButtonConfigs.forEach(config => {
            const button = document.createElement("button");
            button.textContent = `+ ${config.minutes} минут${getMinuteEnding(config.minutes)} - ${config.cost} рублей`;
            button.addEventListener("click", function() {
                addMinutesToActiveSplit(config.minutes);
            });
            extendButtonsContainer.appendChild(button);
        });

        // --- Добавление гифки ---
        const gifElement = document.createElement("img");
        gifElement.src = overlayGifUrl;
        // CSS стили для гифки определены в блоке styles


        overlay.appendChild(warningMessage);
        overlay.appendChild(splitMessage);
        overlay.appendChild(extendButtonsContainer);
        overlay.appendChild(gifElement); // Добавляем гифку после кнопок

        document.body.appendChild(overlay);
    }

    function getMinuteEnding(count) {
        const lastDigit = count % 10;
        const lastTwoDigits = count % 100;

        if (lastTwoDigits >= 11 && lastTwoDigits <= 14) {
            return '';
        }
        if (lastDigit === 1) {
            return 'а';
        }
        if (lastDigit >= 2 && lastDigit <= 4) {
            return 'ы';
        }
        return '';
    }

    function removeOverlay() {
        if (overlay) {
            overlay.remove();
            overlay = null;
            if (audioPlayer) {
                 audioPlayer.pause();
                 audioPlayer.currentTime = 0;
            }
        }
    }

     function initAudioPlayer() {
         if (splitSoundUrl && splitSoundUrl !== 'ВАША_ПРЯМАЯ_ССЫЛКА_НА_ЗВУКОВОЙ_ФАЙЛ_ТУТ') {
             if (!audioPlayer || audioPlayer.src !== splitSoundUrl) {
                  if (audioPlayer) {
                     audioPlayer.pause();
                     audioPlayer = null;
                  }
                 audioPlayer = new Audio(splitSoundUrl);
                 audioPlayer.preload = 'auto';
                 audioPlayer.onerror = (e) => console.error("YouTube Split: Не удалось загрузить или воспроизвести звук:", e);

                 let savedVolume = localStorage.getItem(localStorageVolumeKey);
                 if (savedVolume !== null) {
                      audioPlayer.volume = parseFloat(savedVolume);
                 } else {
                      audioPlayer.volume = 0.5;
                 }
             }
         } else {
              if (audioPlayer) {
                 audioPlayer.pause();
                 audioPlayer = null;
             }
         }
     }

    function setupElementsAndPanel() {
        injectStyles();
        initAudioPlayer();

        video = document.querySelector("video");
        const primaryContainer = document.querySelector("ytd-watch-flexy #primary");
        const panel = document.getElementById("split-control-panel");

        if (video && primaryContainer) {
             if (!panelAdded) {
                 addControlPanel(primaryContainer);
             } else {
                  ensurePanelPosition();
             }

            if (totalVideoMinutes === null && isFinite(video.duration) && video.duration > 0) {
                totalVideoMinutes = Math.ceil(video.duration / 60);
                 if (panelAdded) {
                     updateSplitStatsDisplay();
                 }
            }

        } else {
            if (panelAdded) {
                 const existingPanel = document.getElementById("split-control-panel");
                 if(existingPanel) existingPanel.remove();
                 panelAdded = false;
            }
             video = null;
             totalVideoMinutes = null;
        }
    }

    if (!setupIntervalId) {
        setupIntervalId = setInterval(setupElementsAndPanel, 500);
    }

    let lastUrl = location.href;
    const urlObserver = new MutationObserver(() => {
        if (location.href !== lastUrl) {
            lastUrl = location.href;

            stopSplitCheckInterval();
            if (setupIntervalId) {
                 clearInterval(setupIntervalId);
                 setupIntervalId = null;
            }

             if (audioPlayer) {
                 audioPlayer.pause();
             }
             removeOverlay();

            const oldPanel = document.getElementById("split-control-panel");
            if (oldPanel) {
                oldPanel.remove();
            }
            const oldStyles = document.getElementById("yt-split-styles");
             if(oldStyles) oldStyles.remove();

            splitMinutes = null;
            totalVideoMinutes = null;
            video = null;
            splitTriggered = false;
            panelAdded = false;

            if (!setupIntervalId) {
                 setupIntervalId = setInterval(setupElementsAndPanel, 500);
            }
        }
    });

     urlObserver.observe(document.body, {
        childList: true,
        subtree: true
     });


    window.addEventListener('beforeunload', function() {
        stopSplitCheckInterval();
        if (setupIntervalId) {
            clearInterval(setupIntervalId);
            setupIntervalId = null;
        }
         if (audioPlayer) {
            audioPlayer.pause();
            audioPlayer = null;
         }
        if (urlObserver) {
             urlObserver.disconnect();
        }
    });


})();