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.

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 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();
        }
    });
})();