Disable YouTube Hotkeys with Modern Settings Page

Disable various YouTube hotkeys, including frame skip, with a modern settings page

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Disable YouTube Hotkeys with Modern Settings Page
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Disable various YouTube hotkeys, including frame skip, with a modern settings page
// @author       You
// @match        *://www.youtube.com/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==


(function() {
    'use strict';

    // Load saved settings or default to enabling all keys
    let settings = GM_getValue('hotkeySettings', {
        disableNumericKeys: false,
        disableSpacebar: false,
        disableArrowKeys: false,
        disableFKey: false,
        disableMKey: false,
        disableSpeedControl: false,
        disableFrameSkip: false
    });

    // Function to handle keydown events and disable selected hotkeys
    window.addEventListener('keydown', function(e) {
        // Disable numeric keys (0-9)
        if (settings.disableNumericKeys && e.key >= '0' && e.key <= '9') {
            e.stopPropagation();
            e.preventDefault();
        }

        // Disable spacebar
        if (settings.disableSpacebar && e.code === 'Space') {
            e.stopPropagation();
            e.preventDefault();
        }

        // Disable arrow keys (left, right, up, down)
        if (settings.disableArrowKeys && ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(e.code)) {
            e.stopPropagation();
            e.preventDefault();
        }

        // Disable F (for fullscreen)
        if (settings.disableFKey && e.key.toLowerCase() === 'f') {
            e.stopPropagation();
            e.preventDefault();
        }

        // Disable M (for mute)
        if (settings.disableMKey && e.key.toLowerCase() === 'm') {
            e.stopPropagation();
            e.preventDefault();
        }

        // Disable speed control (Shift + > or Shift + <)
        if (settings.disableSpeedControl && (e.shiftKey && (e.key === '>' || e.key === '<'))) {
            e.stopPropagation();
            e.preventDefault();
        }

        // Disable frame skip (`,` for backward, `.` for forward)
        if (settings.disableFrameSkip && (e.key === ',' || e.key === '.')) {
            e.stopPropagation();
            e.preventDefault();
        }
    }, true);

    // Create and display the settings modal using safe DOM manipulation
    function openSettings() {
        // Remove any existing modal or overlay
        let existingModal = document.getElementById('yt-hotkey-settings-modal');
        let existingOverlay = document.getElementById('yt-hotkey-settings-overlay');
        if (existingModal) existingModal.remove();
        if (existingOverlay) existingOverlay.remove();

        // Create the modal container
        let modal = document.createElement('div');
        modal.id = 'yt-hotkey-settings-modal';
        modal.className = 'modal-card';

        // Create modal header
        let header = document.createElement('div');
        header.className = 'modal-header';

        let title = document.createElement('h2');
        title.textContent = 'YouTube Hotkey Settings';
        header.appendChild(title);

        let closeButton = document.createElement('span');
        closeButton.id = 'closeSettingsBtn';
        closeButton.className = 'close-btn';
        closeButton.textContent = '×';
        header.appendChild(closeButton);

        modal.appendChild(header);

        // Create modal content (checkboxes)
        let content = document.createElement('div');
        content.className = 'modal-content';

        let checkboxes = [
            { id: 'disableNumericKeys', label: 'Disable Numeric Keys (0-9)', checked: settings.disableNumericKeys },
            { id: 'disableSpacebar', label: 'Disable Spacebar (Play/Pause)', checked: settings.disableSpacebar },
            { id: 'disableArrowKeys', label: 'Disable Arrow Keys (Rewind/FF, Volume)', checked: settings.disableArrowKeys },
            { id: 'disableFKey', label: 'Disable F Key (Fullscreen)', checked: settings.disableFKey },
            { id: 'disableMKey', label: 'Disable M Key (Mute)', checked: settings.disableMKey },
            { id: 'disableSpeedControl', label: 'Disable Speed Control (Shift + > / <)', checked: settings.disableSpeedControl },
            { id: 'disableFrameSkip', label: 'Disable Frame Skip (`,` and `.`)', checked: settings.disableFrameSkip }
        ];

        checkboxes.forEach(hotkey => {
            let label = document.createElement('label');
            label.className = 'custom-checkbox';

            let checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.id = hotkey.id;
            checkbox.checked = hotkey.checked;

            let checkmark = document.createElement('span');
            checkmark.className = 'checkmark';

            label.appendChild(checkbox);
            label.appendChild(checkmark);
            label.appendChild(document.createTextNode(` ${hotkey.label}`));
            content.appendChild(label);
        });

        modal.appendChild(content);

        // Create modal footer (save button)
        let footer = document.createElement('div');
        footer.className = 'modal-footer';

        let saveButton = document.createElement('button');
        saveButton.id = 'saveSettingsBtn';
        saveButton.className = 'primary-btn';
        saveButton.textContent = 'Save Settings';
        footer.appendChild(saveButton);

        modal.appendChild(footer);

        // Append modal to the document body
        document.body.appendChild(modal);

        // Create the overlay (dark background behind modal)
        let overlay = document.createElement('div');
        overlay.id = 'yt-hotkey-settings-overlay';
        overlay.className = 'modal-overlay';
        document.body.appendChild(overlay);

        // Close modal on clicking the close button or overlay
        closeButton.addEventListener('click', closeSettings);
        overlay.addEventListener('click', closeSettings);

        // Save settings on clicking the save button
        saveButton.addEventListener('click', function() {
            settings.disableNumericKeys = document.getElementById('disableNumericKeys').checked;
            settings.disableSpacebar = document.getElementById('disableSpacebar').checked;
            settings.disableArrowKeys = document.getElementById('disableArrowKeys').checked;
            settings.disableFKey = document.getElementById('disableFKey').checked;
            settings.disableMKey = document.getElementById('disableMKey').checked;
            settings.disableSpeedControl = document.getElementById('disableSpeedControl').checked;
            settings.disableFrameSkip = document.getElementById('disableFrameSkip').checked;
            GM_setValue('hotkeySettings', settings);

            // Show a success message and close modal after a short delay
            showNotification('Settings saved successfully!', modal);
            setTimeout(closeSettings, 1500);
        });

        // Function to close the settings modal
        function closeSettings() {
            modal.remove();
            overlay.remove();
        }
    }

    // Function to show a notification banner
    function showNotification(message, parentElement) {
        let banner = document.createElement('div');
        banner.className = 'notification-banner';
        banner.textContent = message;
        parentElement.appendChild(banner);

        setTimeout(() => banner.remove(), 3000);
    }

    // Register the settings menu command
    GM_registerMenuCommand('YouTube Hotkey Settings', openSettings);

    // Add styles for the modal and modern UI
    GM_addStyle(`
        /* General Modal Styling */
        .modal-card {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: #fff;
            border-radius: 10px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
            width: 400px;
            max-width: 90%;
            z-index: 10001;
            overflow: hidden;
            animation: slide-down 0.3s ease-out;
        }

        .modal-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            z-index: 10000;
        }

        .modal-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            background-color: #007bff;
            color: white;
            padding: 15px;
        }

        .modal-content {
            padding: 20px;
        }

        .modal-footer {
            display: flex;
            justify-content: flex-end;
            padding: 10px;
            border-top: 1px solid #ddd;
        }

        /* Checkbox Styling */
        .custom-checkbox {
            display: flex;
            align-items: center;
            margin-bottom: 10px;
            font-size: 16px;
        }

        .custom-checkbox input[type="checkbox"] {
            display: none;
        }

        .custom-checkbox .checkmark {
            display: inline-block;
            width: 18px;
            height: 18px;
            border: 2px solid #007bff;
            border-radius: 3px;
            margin-right: 10px;
            transition: all 0.2s;
        }

        .custom-checkbox input[type="checkbox"]:checked + .checkmark {
            background-color: #007bff;
            border-color: #007bff;
        }

        /* Button Styling */
        .primary-btn {
            background-color: #007bff;
            color: white;
            border: none;
            border-radius: 5px;
            padding: 10px 20px;
            cursor: pointer;
            transition: background-color 0.2s;
        }

        .primary-btn:hover {
            background-color: #0056b3;
        }

        /* Close Button */
        .close-btn {
            font-size: 24px;
            color: white;
            cursor: pointer;
            padding: 0 10px;
        }

        /* Notification Banner */
        .notification-banner {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            padding: 10px;
            background-color: #28a745;
            color: white;
            text-align: center;
            animation: fade-in-out 3s ease-out;
        }

        /* Animations */
        @keyframes slide-down {
            from { transform: translate(-50%, -60%); opacity: 0; }
            to { transform: translate(-50%, -50%); opacity: 1; }
        }

        @keyframes fade-in-out {
            0%, 100% { opacity: 0; }
            20%, 80% { opacity: 1; }
        }
    `);
})();