// ==UserScript== // @name YouTube Split (Improved + Stats + Fixed + GIF) // @namespace http://tampermonkey.net/ // @version 2.2 // Повышаем версию // @author ChatGPT (based on user's code) // @match *://www.youtube.com/watch* // @grant none // @description Устанавливает сплит по времени, панель управления под видео, показывает статистику "Выкуплено/Всего минут", оверлей "СПЛИТ НЕ ОПЛАЧЕН" с гифкой и проигрывает звук при достижении порога. // @downloadURL none // ==/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(); } }); })();