YouTube Like/Dislike Shortcut

Enables keyboard shortcuts to like/dislike a video on YouTube.

// ==UserScript==
// @name               YouTube Like/Dislike Shortcut
// @name:pt-BR         Atalhos Gostei/Não Gostei no YouTube
// @namespace          will64gamer
// @author             will64gamer
// @description        Enables keyboard shortcuts to like/dislike a video on YouTube.
// @description:pt-BR  Cria atalhos para os botões gostei/não gostei em um vídeo no YouTube.
// @match              *://*.youtube.com/*
// @license            MIT
// @version            2.4.1
// ==/UserScript==

// You can change the codes to whichever keys you want to use for liking, disliking, and opening or writing comments on Shorts.
const codeLike = "NumpadAdd";
const codeDislike = "NumpadSubtract";
const codeComments = "NumpadMultiply";
const codeWrite = "NumpadDivide";

// Change this to false if you don't want Shorts to refresh on load.
const shortsRefresh = true;

// Change this to 0 if you don't want the sound alert, or 2 if you want it on Shorts as well.
const alertPlays = 1;

// You can change the sound for the alert by using your own audio file URL or base64 encoded data.
const alertSound = new Audio(`
  data:audio/mp3; base64,
  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
`);

// Change this value if you want to change the alert volume percentage (1 is 100%, 0.25 is 25%).
alertSound.volume = 0.5;

//    /\/\/\   Settings   /\/\/\
//  ------------------------------
//    \/\/\/     Code     \/\/\/

let isShort = false;
let tag, like, dislike, sLike, sDislike, comments, closeComments, textbox, initialRefresh;

const observer = new MutationObserver(findButtons);

addEventListener('yt-page-data-updated', reset);
addEventListener('popstate', reset);

function reset() {
  isVideo = /^\/watch/.test(location.pathname);
  isShort = /^\/shorts/.test(location.pathname);
  if (isVideo||isShort) {
    removeEventListener("keydown", press);
    like = null; dislike = null; sLike = null; sDislike = null; comments = null; closeComments = null; textbox = null;
    observer.observe(document.documentElement, {childList: true, subtree: true});
    findButtons();
  } else {initialRefresh = true;}
}

function findButtons() {
  if ((like && dislike) || (sLike && sDislike && comments)) {
    addEventListener("keydown", press);
    observer.disconnect();
    let currentURL = location.href;
    if (isShort && shortsRefresh) {
      let shortInterval = setInterval(() => {
        if (location.href !== currentURL) {
          clearInterval(shortInterval);
          location.reload();
          }
        }, 400);
    } else if (isVideo) {initialRefresh = true;}
  }

  if (isShort) {
    if (initialRefresh) {
      initialRefresh = false;
      location.reload();
    }
    sLike = document.getElementById("like-button")?.getElementsByTagName('button')[0];
    sDislike = document.getElementById("dislike-button")?.getElementsByTagName('button')[0];
    comments = document.getElementById("comments-button")?.getElementsByTagName('button')[0];
  } else {
    like = document.getElementsByTagName("like-button-view-model")[0]?.firstElementChild?.firstElementChild?.firstElementChild;
    dislike = document.getElementsByTagName("dislike-button-view-model")[0]?.firstElementChild?.firstElementChild?.firstElementChild;
  }
}

function openComments() {
  comments.click();
  let closeCommentsInterval = setInterval(() => {
    if (!closeComments) {
      closeComments = document.getElementById("visibility-button")?.getElementsByTagName('button')[0];
    } else {
      clearInterval(closeCommentsInterval);
    }
  }, 300);
}

function press(e) {
  if (e.target.getAttribute("contenteditable") === "true") {return;}

  tag = e.target.tagName.toLowerCase();
  if (tag === "input" || tag === "textarea") {return;}

  switch (e.code) {
    case codeComments:
      if (comments) {
        if (closeComments) {
          closeComments.click();
          closeComments = null;
        } else {
          openComments();
        }
      }
      break;
    case codeWrite:
      if (textbox) {
        openComments();
        setTimeout(() => {
          textbox.click();
        }, 100);
      } else if (comments) {
        openComments();
        let textboxInterval = setInterval(() => {
          if (!textbox) {
            textbox = document.getElementById("simplebox-placeholder");
          } else {
            textbox.focus();
            clearInterval(textboxInterval);
          }
        }, 300);
      }
      break;
    case codeLike:
      if (like) {
        if (alertPlays !== 0) {alert(like);}
        like.click();
      }
      else if (sLike) {
        if (alertPlays === 2) {alert(sLike);}
        sLike.click();
      }
      break;
    case codeDislike:
      if (dislike) {dislike.click();}
      else if (sDislike) {sDislike.click();}
      break;
  }
}

function alert(likeTag) {
  if (likeTag.getAttribute("aria-pressed") === "false") {
    alertSound.play();
  }
}