Video Sites Shortcuts

Add keyboard shortcuts to various video sites

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Video Sites Shortcuts
// @namespace    http://tampermonkey.net/
// @version      1.0
// @license      Unlicense
// @description  Add keyboard shortcuts to various video sites
// @author       Jim Chen
// @homepage     https://jimchen.me
// @match        *://*.bilibili.com/*
// @match        *://vkvideo.ru/*
// @match        *://rutube.ru/*
// @match        *://*.1tv.ru/*
// @match        *://*.matchtv.ru/*
// @match        file://*/*
// @run-at       document-idle
// ==/UserScript==
(function () {
  document.addEventListener("keydown", function (e) {
    const activeElement = document.activeElement;
    const isInputElement = activeElement && /input|textarea/i.test(activeElement.tagName);

    // If typing in an input/textarea, only allow Escape to blur
    if (isInputElement) {
      if (e.key === "Escape") {
        activeElement.blur();
      }
      return;
    }

    const hostname = window.location.hostname;
    const isBilibili = hostname.includes("bilibili.com");
    const isVkVideo = hostname.includes("vkvideo.ru");
    const isRuTube = hostname.includes("rutube.ru");

    // --- Search Focus ---
    if (e.key === "/" || e.key === ".") {
      let searchInput = null;

      if (isBilibili) {
        const selector = window.location.href.startsWith("https://search.bilibili.com/")
          ? 'input.search-input-el[placeholder="输入关键字搜索"]' // More specific selector for search page
          : ".nav-search-input"; // General header search
        searchInput = document.querySelector(selector);
      } else if (isVkVideo) {
        // VK Video specific selector
        searchInput = document.querySelector("#video_service_search_input");
      } else if (isRuTube) {
        // RuTube specific selector for search input
        searchInput = document.querySelector(".wdp-search-line-module__input");
      }

      if (searchInput) {
        e.preventDefault(); // Prevent browser's default find action
        searchInput.focus();
        searchInput.select(); // Select existing text
      }
      return;
    }

    const video = document.querySelector("video");
    if (!video) return;

    // Number keys 0-9 for seeking
    if (/^[0-9]$/.test(e.key)) {
      e.preventDefault();
      const percentage = parseInt(e.key) * 10;
      if (video.duration && isFinite(video.duration)) {
        video.currentTime = (percentage / 100) * video.duration;
      }
      return;
    }

    let keyHandled = false;

    if (e.shiftKey && (e.key === ">" || e.key === "Ю")) {
      // Increase speed
      video.playbackRate = Math.min(video.playbackRate + 0.25, 4); // Capped at 4x
      keyHandled = true;
    } else if (e.shiftKey && (e.key === "<" || e.key === "Б")) {
      // Decrease speed
      video.playbackRate = Math.max(video.playbackRate - 0.25, 0.25); // Minimum 0.25x
      keyHandled = true;
    } else if (e.key === "j" || e.key === "J" || e.key === "о" || e.key === "О") {
      video.currentTime = Math.max(video.currentTime - 10, 0);
      keyHandled = true;
    } else if (e.key === "k" || e.key === "K" || e.key === "л" || e.key === "Л") {
      if (video.paused) {
        video.play().catch((err) => console.error("Play failed:", err)); // Add error handling for play()
      } else {
        video.pause();
      }
      keyHandled = true;
    } else if (e.key === "l" || e.key === "L" || e.key === "д" || e.key === "Д") {
      if (video.duration && isFinite(video.duration)) {
        video.currentTime = Math.min(video.currentTime + 10, video.duration);
      }
      keyHandled = true;
    }
    if (keyHandled) {
      e.preventDefault();
    }
  });
})();