Instagram Keyboard Shortcuts for Power User

Scroll through posts with J/K keys, like with L, save with O, control videos with U/I/M/Space.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Instagram Keyboard Shortcuts for Power User
// @namespace    http://tampermonkey.net/
// @version      2.6.1
// @description  Scroll through posts with J/K keys, like with L, save with O, control videos with U/I/M/Space.
// @author       French Bond
// @license      MIT
// @match        https://www.instagram.com/*
// @grant        none
// @icon         https://www.google.com/s2/favicons?sz=64&domain=instagram.com
// @require      https://code.jquery.com/jquery-3.4.1.min.js
// ==/UserScript==

// Credits:
// Thanks to Med X for improvements to input field focus tracking

/* globals jQuery, $ */
/* jshint esversion: 6 */

$(function () {
  'use strict';

  let seeAll = true;
  let currentArticle = null;
  let searchFocused = false;
  let scrolling = false;
  let slideshowInterval;
  let isSlideshow = false;

  const fastForward = 2;
  const rewind = 1;
  const headerHeight = 10;
  const scrollSpeed = 200;

  // ----------------------------
  // Helper Functions
  // ----------------------------

  function startSlideshow() {
    isSlideshow = true;
    slideshowInterval = setInterval(() => {
      findAndClickButton('Next');
    }, 5000);
  }

  function stopSlideshow() {
    isSlideshow = false;
    clearInterval(slideshowInterval);
  }

  // Track focus on input fields to avoid interfering with typing
  $(document).on(
    'focus',
    'input, textarea, [contenteditable="true"], [role="textbox"]',
    () => {
      searchFocused = true;
    }
  );
  $(document).on(
    'blur',
    'input, textarea, [contenteditable="true"], [role="textbox"]',
    () => {
      searchFocused = false;
    }
  );

  // Function to determine the current page
  function getCurrentPage() {
    const url = window.location.href;
    if (url === 'https://www.instagram.com/') return 'home';
    if (url.includes('?variant=favorites')) return 'favorites';
    if (url.includes('?variant=following')) return 'following';
    if (url.includes('/reels/')) return 'reels';
    if (url.includes('/p/')) return 'post';
    if (url.includes('/saved/')) return 'saved';
    if (url.includes('/explore/')) return 'explore';
    if (url.includes('/accounts/')) return 'profile';
    if (url.includes('/explore/tags/')) return 'tag';
    if (url.includes('/explore/locations/')) return 'location';
    if (url.includes('/tv/')) return 'igtv';
    return 'unknown';
  }

  // Function to check if an element is visible
  function isVisible(element) {
    if (!element) return false;

    const style = window.getComputedStyle(element);

    // Check if the element has zero size
    const hasSize = !!(
      element.offsetWidth ||
      element.offsetHeight ||
      element.getClientRects().length
    );

    // Check visibility-related CSS properties
    const isNotHidden =
      style.display !== 'none' &&
      style.visibility !== 'hidden' &&
      style.opacity !== '0';

    return hasSize && isNotHidden;
  }

  // Function to find and control video
  function findAndControlVideo(action) {
    const video = $(currentArticle).find('video')[0];
    if (video) {
      switch (action) {
        case 'playPause':
          video.paused ? video.play() : video.pause();
          break;
        case 'rewind':
          video.currentTime -= rewind;
          break;
        case 'fastForward':
          video.currentTime += fastForward;
          break;
        case 'muteUnmute':
          const muteButton = $(currentArticle).find(
            '[aria-label="Toggle audio"]'
          );
          if (muteButton.length) muteButton.click();
          break;
      }
    }
  }

  function findTopVideo() {
    let closestVideo = null;
    let closestDistance = Infinity;
    $('video').each(function () {
      const rect = this.getBoundingClientRect();
      const distance = Math.abs(rect.top);
      if (distance < closestDistance) {
        closestDistance = distance;
        closestVideo = this;
      }
    });
    return closestVideo;
  }

  function scrollTo(pageY) {
    scrolling = true;
    $('html, body').animate(
      { scrollTop: pageY },
      {
        duration: scrollSpeed,
        done: () => {
          scrolling = false;
        },
      }
    );
  }

  // Function to find and click a button
  function findAndClickButton(ariaLabel) {
    const button =
      document.querySelector(`article button[aria-label="${ariaLabel}"]`) ||
      document.querySelector(`button:has(svg[aria-label="${ariaLabel}"])`) ||
      document.querySelector(`div.html-div button[aria-label="${ariaLabel}"]`);

    button?.click();
  }

  // ----------------------------
  // Keyboard Shortcuts
  // ----------------------------

  function homeKeyboardShortcuts(e) {
    switch (e.keyCode) {
      case 65: // A - Toggle see all
        seeAll = !seeAll;
        break;
      case 74: // J - Scroll down or next post
        if (seeAll && $(currentArticle).find('[aria-label="Next"]').length) {
          $(currentArticle).find('[aria-label="Next"]').click();
        } else {
          $('article').each(function (index, article) {
            const top = $(article).offset().top - headerHeight;
            if (isVisible(article) && top > $(window).scrollTop() + 1) {
              scrollTo(top);
              currentArticle = article;
              return false;
            }
          });
        }
        break;
      case 75: // K - Scroll up
        if (seeAll && $(currentArticle).find('[aria-label="Go Back"]').length) {
          $(currentArticle).find('[aria-label="Go Back"]').click();
        } else {
          let previousArticle = null;
          $('article').each(function (index, article) {
            const top = $(article).offset().top - headerHeight;
            if (
              isVisible(article) &&
              top > $(window).scrollTop() - headerHeight - 20
            ) {
              if (previousArticle) {
                scrollTo($(previousArticle).offset().top - headerHeight);
                currentArticle = previousArticle;
              }
              return false;
            }
            previousArticle = article;
          });
        }
        break;
      case 76: // L - Like
        $('[aria-label="Like"],[aria-label="Unlike"]', currentArticle)
          .parent()
          .click();
        break;
      case 79: // O - Save
        const firstElement = $(
          '[aria-label="Save"],[aria-label="Remove"]',
          currentArticle
        )[0];
        $(firstElement).parent().click();
        break;
      case 32: // Space - Play/pause video
        findAndControlVideo('playPause');
        break;
      case 85: // U - Rewind
        findAndControlVideo('rewind');
        break;
      case 73: // I - Fast forward
        findAndControlVideo('fastForward');
        break;
      case 77: // M - Mute/unmute video
        findAndControlVideo('muteUnmute');
        break;
    }
  }

  function postKeyboardShortcuts(e) {
    switch (e.keyCode) {
      case 74: // J - Next
        findAndClickButton('Next');
        break;
      case 75: // K - Previous
        findAndClickButton('Go back');
        break;
      case 32: // Space - Toggle slideshow
        isSlideshow ? stopSlideshow() : startSlideshow();
        break;
    }
  }

  // Keyboard shortcuts for Reels page
  function reelsKeyboardShortcuts(e) {
    switch (e.keyCode) {
      case 39: // Right arrow - Fast forward
        const videoFF = findTopVideo();
        if (videoFF) videoFF.currentTime += fastForward;
        break;
      case 37: // Left arrow - Rewind
        const videoRW = findTopVideo();
        if (videoRW) videoRW.currentTime -= rewind;
        break;
    }
  }

  // ----------------------------
  // Main Keydown Handler
  // ----------------------------
  $('body').keydown(function (e) {
    // Disable if searchFocused, scrolling, or focus is in a contenteditable element (or its parent)
    if (
      searchFocused ||
      scrolling ||
      $(e.target).closest(
        '[contenteditable="true"], input, textarea, [role="textbox"]'
      ).length > 0
    )
      return;

    // Ignore browser/system shortcuts
    if (e.ctrlKey || e.altKey || e.metaKey) return;

    const handledKeys = [65, 74, 75, 76, 79, 32, 85, 73, 77, 37, 39]; // A, J, K, L, O, Space, U, I, M, Left, Right
    if (handledKeys.includes(e.keyCode)) {
      e.preventDefault();
      document.activeElement.blur();
    }

    const currentPage = getCurrentPage();
    console.log('Current page:', currentPage);

    switch (currentPage) {
      case 'home':
      case 'favorites':
      case 'following':
        homeKeyboardShortcuts(e);
        break;
      case 'reels':
        reelsKeyboardShortcuts(e);
        break;
      case 'post':
        postKeyboardShortcuts(e);
        break;
    }
  });
});