[v4.3] YouTube Music - Skip Liked/Disliked & Pause (with Libraries)

Автоматически пропускает лайкнутые/дизлайкнутые песни. Клавиша 'D' для дизлайка. Внедрен "иммунитет" для стабильного пропуска.

// ==UserScript==
// @name         [v4.3] YouTube Music - Skip Liked/Disliked & Pause (with Libraries)
// @namespace    http://tampermonkey.net/
// @version      4.3
// @description  Автоматически пропускает лайкнутые/дизлайкнутые песни. Клавиша 'D' для дизлайка. Внедрен "иммунитет" для стабильного пропуска.
// @author       torch (исправлено Gemini)
// @match        https://music.youtube.com/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dayjs.min.js
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Helper для логгирования с временной меткой
    const logWithTimestamp = (message, isWarning = false) => {
        const timestamp = dayjs().format('HH:mm:ss');
        const logFunction = isWarning ? console.warn : console.log;
        logFunction(`[${timestamp}] ${message}`);
    };

    logWithTimestamp("[YT Music Skipper] Version 4.3 (with Lodash & Day.js) loaded.");

    let isScriptPaused = false;
    let currentSongTitle = "";
    let isSkipping = false; // Флаг "иммунитета" после пропуска

    // =================================================
    // ЧАСТЬ 0: УПРАВЛЕНИЕ СОСТОЯНИЕМ СКРИПТА
    // =================================================

    function createPauseButton() {
        try {
            const controlsContainer = document.createElement('div');
            controlsContainer.id = 'userscript-controls';
            const statusSpan = document.createElement('span');
            statusSpan.id = 'script-status';
            statusSpan.textContent = 'Статус: Активен';
            const toggleButton = document.createElement('button');
            toggleButton.id = 'toggle-script-btn';
            toggleButton.textContent = 'Пауза';

            controlsContainer.append(statusSpan, toggleButton);
            document.body.appendChild(controlsContainer);

            toggleButton.addEventListener('click', () => {
                isScriptPaused = !isScriptPaused;
                statusSpan.textContent = `Статус: ${isScriptPaused ? 'Пауза' : 'Активен'}`;
                toggleButton.textContent = isScriptPaused ? 'Возобновить' : 'Пауза';

                logWithTimestamp(`Скрипт ${isScriptPaused ? 'приостановлен' : 'возобновлен'}.`);
                if (!isScriptPaused) checkCurrentTrack();
            });
        } catch (e) {
            logWithTimestamp(`Не удалось создать кнопку управления: ${e}`, true);
        }
    }

    GM_addStyle(`
        #userscript-controls {
            position: fixed; bottom: 15px; right: 15px; z-index: 10001;
            background-color: rgba(28, 28, 28, 0.95); border: 1px solid #555;
            border-radius: 8px; padding: 10px; display: flex; align-items: center;
            font-family: 'Roboto', 'Arial', sans-serif; color: white;
            box-shadow: 0 4px 12px rgba(0,0,0,0.6);
        }
        #script-status { margin-right: 12px; font-size: 14px; }
        #toggle-script-btn {
            background-color: #f44336; color: white; border: none; padding: 8px 12px;
            font-size: 14px; border-radius: 5px; cursor: pointer; transition: background-color 0.3s;
        }
        #toggle-script-btn:hover { background-color: #d32f2f; }
    `);

    // =================================================
    // ЧАСТЬ 1: АВТОМАТИЧЕСКИЙ ПРОПУСК ПЕСЕН
    // =================================================

    function skipTrackIfNecessary(songTitle) {
        if (isScriptPaused || isSkipping) return;

        let attempts = 0;
        const maxAttempts = 15; // 1.5 секунды
        const checkInterval = setInterval(() => {
            if (isScriptPaused || attempts++ >= maxAttempts) {
                clearInterval(checkInterval);
                return;
            }

            const likeStatus = document.querySelector('ytmusic-player-bar ytmusic-like-button-renderer')?.getAttribute('like-status');
            const skipButton = document.querySelector('ytmusic-player-bar .next-button');

            if (!likeStatus) return;

            if (likeStatus === 'LIKE' || likeStatus === 'DISLIKE') {
                if (skipButton && skipButton.offsetParent !== null) {
                    clearInterval(checkInterval); // Прекращаем проверку

                    logWithTimestamp(`Пропускаем трек: "${songTitle}". Статус: ${likeStatus}.`);
                    isSkipping = true; // Активируем "иммунитет"
                    skipButton.click();

                    // Снимаем "иммунитет" через 1.5 секунды и проверяем новый трек
                    setTimeout(() => {
                        logWithTimestamp("Период 'иммунитета' завершен.");
                        isSkipping = false;
                        checkCurrentTrack.cancel(); // Отменяем любые отложенные вызовы
                        checkCurrentTrack();      // И вызываем проверку немедленно для нового трека
                    }, 1500);
                }
            } else if (likeStatus === 'INDIFFERENT') {
                clearInterval(checkInterval);
            }
        }, 100);
    }

    const checkCurrentTrack = _.debounce(() => {
        // Проверка флагов в начале функции
        if (isScriptPaused || isSkipping) {
            return;
        }
        const titleElement = document.querySelector('ytmusic-player-bar .title.style-scope.ytmusic-player-bar');
        const newTitle = titleElement?.getAttribute('title');

        if (newTitle && newTitle !== currentSongTitle) {
            currentSongTitle = newTitle;
            logWithTimestamp(`Новый трек: "${newTitle}". Начинаем проверку статуса...`);
            skipTrackIfNecessary(newTitle);
        }
    }, 250, { leading: false, trailing: true }); // Немного увеличил debounce для большей стабильности

    // =================================================
    // ЧАСТЬ 2: ДИЗЛАЙК ПО КЛАВИШЕ "D"
    // =================================================

    document.addEventListener('keydown', (e) => {
        if (isScriptPaused) return;
        const activeElement = document.activeElement;
        const isInput = activeElement.tagName === 'INPUT' || activeElement.closest('ytmusic-search-box') || activeElement.isContentEditable;

        if (e.key.toUpperCase() === 'D' && !isInput) {
            e.preventDefault();
            const dislikeButton = document.querySelector('ytmusic-player-bar #button-shape-dislike button');
            if (dislikeButton) {
                const songTitleForLog = document.querySelector('ytmusic-player-bar .title').getAttribute('title');
                logWithTimestamp(`Дизлайк треку: "${songTitleForLog}"`);
                dislikeButton.click();
            }
        }
    });

    // =================================================
    // ЗАПУСК СКРИПТА
    // =================================================

    function initialize() {
        createPauseButton();
        const targetNode = document.querySelector('ytmusic-player-bar .title.style-scope.ytmusic-player-bar');
        if (targetNode) {
            logWithTimestamp("Плеер обнаружен. Запускаем MutationObserver.");
            const observer = new MutationObserver(checkCurrentTrack);
            observer.observe(targetNode, { attributes: true, attributeFilter: ['title'] });
            checkCurrentTrack();
        } else {
            setTimeout(initialize, 500);
        }
    }

    if (document.readyState === 'complete') {
        initialize();
    } else {
        window.addEventListener('load', initialize);
    }

})();