YouTube Key Navigation

Add fast, customizable keyboard shortcuts to YouTube. Instantly open Home, Watch Later, and History, or perform actions like Like, Dislike, Subscribe, Toggle Notifications, and Voice Search — all without refreshing the page. Fully configurable keybindings for seamless YouTube navigation and control.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         YouTube Key Navigation
// @namespace    http://tampermonkey.net/
// @version      1.1
// @description  Add fast, customizable keyboard shortcuts to YouTube. Instantly open Home, Watch Later, and History, or perform actions like Like, Dislike, Subscribe, Toggle Notifications, and Voice Search — all without refreshing the page. Fully configurable keybindings for seamless YouTube navigation and control.
// @icon               https://www.google.com/s2/favicons?sz=64&domain=youtube.com

// @author       Anurag Kashyap
// @match        *://www.youtube.com/*
// @match        *://youtube.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    /**********************************************************
     * 🛠 KEYBIND CONFIG - EDIT THIS PART ONLY
     *
     * Just change the keys in the arrays below.
     * Use lowercase for convenience; script will match both
     * lowercase and uppercase automatically.
     **********************************************************/
    const KEYBINDS = {
        home: ['u'],           // h → go to Home
        watchLater: ['q'],     // . → go to Watch Later
        history: ['h'],        // q → go to History
        notifications: ['z'],  // z → toggle notifications
        voiceSearch: ['x'],    // x → search with your voice
        like: ['b'],           // v → like video
        dislike: ['n'],        // n → dislike video
        subscribe: ['e'],      // b → subscribe to channel
    };
    /**********************************************************/

    // Utility: Check if user is typing in an input/textarea/contenteditable
    function isTypingInInput(e) {
        const target = e.target;
        if (!target) return false;

        const tag = target.tagName;
        const editable = target.isContentEditable;

        return (
            editable ||
            tag === 'INPUT' ||
            tag === 'TEXTAREA' ||
            tag === 'SELECT'
        );
    }

    // Utility: Try multiple selectors until one works
    function clickFirstSelector(selectors) {
        for (const selector of selectors) {
            const el = document.querySelector(selector);
            if (el) {
                el.click();
                return true;
            }
        }
        return false;
    }

    // NAVIGATION HELPERS
    function goHome() {
        const success = clickFirstSelector([
            'a[title="Home"]',
            'a[aria-label="Home"]',
            'a[endpoint*="FEwhat_to_watch"]',
            'a[href="/"]'
        ]);

        if (!success) console.warn('Home link not found.');
    }

    function goWatchLater() {
        const success = clickFirstSelector([
            'a[title="Watch later"]',
            'a[aria-label^="Watch later"]',
            'a[href*="&list=WL"]',
            'a[href*="playlist?list=WL"]'
        ]);

        if (!success) console.warn('Watch later link not found.');
    }

    function goHistory() {
        const success = clickFirstSelector([
            'a[title="History"]',
            'a[aria-label="History"]',
            'a[href="/feed/history"]'
        ]);

        if (!success) console.warn('History link not found.');
    }

    // ACTION HELPERS
   function toggleNotifications() {
    // Prefer the top-right notifications bell in the masthead
    let btn = document.querySelector('ytd-notification-topbar-button-renderer button');

    // Fallback: search all "Notifications" buttons but keep only the one in the topbar
    if (!btn) {
        const candidates = Array.from(
            document.querySelectorAll('button[aria-label*="Notification"]')
        );

        btn = candidates.find(el =>
            el.closest('ytd-notification-topbar-button-renderer') ||
            el.closest('ytd-masthead')
        );
    }

    if (btn) {
        btn.click();
    } else {
        console.warn('Topbar notification button not found.');
    }
}


    function voiceSearch() {
        const success = clickFirstSelector([
            'button[aria-label="Search with your voice"]',
            'button[aria-label*="voice"]',
            'ytd-microphone-button button'
        ]);

        if (!success) console.warn('Voice search button not found.');
    }

    function likeVideo() {
        const success = clickFirstSelector([
            'ytd-segmented-like-dislike-button-renderer yt-button-shape:nth-of-type(1) button',
            'button[aria-label^="Like this video"]',
            'button[aria-pressed][aria-label^="like this video" i]'
        ]);

        if (!success) console.warn('Like button not found.');
    }

    function dislikeVideo() {
        const success = clickFirstSelector([
            'ytd-segmented-like-dislike-button-renderer yt-button-shape:nth-of-type(2) button',
            'button[aria-label^="Dislike this video"]',
            'button[aria-pressed][aria-label^="dislike this video" i]'
        ]);

        if (!success) console.warn('Dislike button not found.');
    }

    function subscribeChannel() {
        const btn = document.querySelector(
            'ytd-subscribe-button-renderer tp-yt-paper-button,' +
            'ytd-subscribe-button-renderer button,' +
            'button[aria-label^="Subscribe"],' +
            'button[aria-label^="Subscribed"]'
        );

        if (!btn) {
            console.warn('Subscribe button not found.');
            return;
        }

        const pressed = btn.getAttribute('aria-pressed');
        if (pressed === 'true') {
            console.log('Already subscribed (aria-pressed=true). No action taken.');
            return;
        }

        btn.click();
    }

    // Build key → action map from KEYBINDS config
    const keyToAction = {};
    function registerKeys() {
        const actions = {
            home: goHome,
            watchLater: goWatchLater,
            history: goHistory,
            notifications: toggleNotifications,
            voiceSearch: voiceSearch,
            like: likeVideo,
            dislike: dislikeVideo,
            subscribe: subscribeChannel,
        };

        for (const [actionName, keys] of Object.entries(KEYBINDS)) {
            const handler = actions[actionName];
            if (!handler) continue;

            keys.forEach(k => {
                if (!k) return;
                const key = String(k).toLowerCase();
                keyToAction[key] = handler;
            });
        }
    }

    registerKeys();

    // KEY LISTENER
    window.addEventListener('keydown', function (e) {
        if (!/\.youtube\.com$/.test(location.hostname)) return;
        if (e.altKey || e.ctrlKey || e.metaKey) return;
        if (isTypingInInput(e)) return;

        const key = e.key.toLowerCase();
        const action = keyToAction[key];

        if (action) {
            e.preventDefault();
            action();
        }
    });
})();