YT: not interested in one click

Hover a thumbnail on youtube.com and click an icon at the right: "Not interested" and "Don't recommend channel"

// ==UserScript==
// @name           YT: not interested in one click
// @description    Hover a thumbnail on youtube.com and click an icon at the right: "Not interested" and "Don't recommend channel"
// @version        1.2.26
//
// @match          https://www.youtube.com/*
//
// @noframes
// @grant          none
//
// @author         wOxxOm
// @namespace      wOxxOm.scripts
// @license        MIT License
// ==/UserScript==
'use strict';

const {
  THUMB_TAG = 'ytd-thumbnail',
  THUMB2_TAG = 'ytd-playlist-thumbnail',
  PREVIEW_TAG = 'ytd-video-preview',
  PREVIEW_PARENT = '#media-container', // parent for the added buttons; must be visible
} = getStorage() || {};
const ME = 'yt-one-click-dismiss';
const MENU = 'ytd-menu-popup-renderer';
const CLOSEST = [THUMB_TAG, PREVIEW_TAG, THUMB2_TAG].join(',');
const COMMANDS = {
  NOT_INTERESTED: 'video',
  REMOVE: 'channel',
  DELETE: 'unwatch',
};
let STYLE;
let inlinable;

addEventListener('click', onClick, true);
addEventListener('mousedown', onClick, true);
addEventListener('mouseover', onHover);
addEventListener('yt-action', ({detail: d}) => {
  if (d.actionName === 'yt-set-cookie-command') {
    inlinable = !d.args[0].setCookieCommand.value;
  }
});

function onHover(e, delayed) {
  const el = e.target.closest(CLOSEST); if (!el) return;
  const inline = el.localName === PREVIEW_TAG;
  const thumb = inline ? $(THUMB_TAG, el) : !el.getElementsByClassName(ME)[0] && el;
  if (thumb && (
    inline ||
    delayed ||
    !(inlinable != null ? inlinable : getProp($(PREVIEW_TAG), 'inlinePreviewIsEnabled')) ||
    setTimeout(getInlineState, 250, e) && false
  )) {
    if (inline) {
      addButtons($(PREVIEW_PARENT, el),
        getProp(el, 'mediaRenderer') ||
        getProp(el, 'opts.mediaRenderer'));
    } else {
      addButtons(thumb, thumb);
    }
  }
}

async function onClick(e) {
  if (e.button) return;
  const me = e.target;
  const thumb = me[ME]; if (!thumb) return;
  const a = me.closest('a');
  e.stopPropagation();
  if (e.type === 'click') return;
  if (a) a.style.setProperty('pointer-events', 'none', 'important');
  await new Promise(r => me.addEventListener('mouseup', r, {once: true}));
  let index, menu, popup, entry, el;
  if ((entry = thumb.parentElement.closest('[class*="-renderer"]'))
  && (el = $('.dropdown-trigger', entry))) {
    await 0;
    index = STYLE.sheet.insertRule(MENU + ':not(#\\0) { opacity: 0 !important }');
    el.dispatchEvent(new Event('click'));
    if ((popup = await waitFor('ytd-popup-container')))
      menu = await waitFor(MENU, popup);
  }
  if (!menu) {
    STYLE.sheet.deleteRule(index);
    el = me.nextSibling;
    me.remove();
    me.title = 'No menu button?\nWait a few seconds for the site to load.';
    await new Promise(setTimeout);
    el.before(me);
    await timedPromise(null, 5000);
    me.title = '';
    return;
  }
  if (me.title)
    me.title = '';
  if (!isMenuReady(menu)) {
    let mo;
    if (!await timedPromise(resolve => {
      mo = new MutationObserver(() => isMenuReady(menu) && resolve(true));
      mo.observe(menu, {attributes: true, attributeFilter: ['style']});
    })) console.warn('Timeout waiting for px in `style` of', menu);
    mo.disconnect();
  }
  await new Promise(setTimeout);
  el = getProp(popup, `popups_.${MENU}.target`, true);
  if (a) a.style.removeProperty('pointer-events');
  if (el && !entry.contains(el)) {
    console.warn('Menu is not for the video you clicked', [menu, entry]);
    STYLE.sheet.deleteRule(index);
    return;
  }
  try {
    for (el of $('[role="listbox"]', menu).children) {
      if (me.dataset.block === (COMMANDS[getProp(el, 'data.icon.iconType')] || {}).block) {
        el.click();
        break;
      }
    }
  } catch (e) {}
  await new Promise(setTimeout);
  document.body.click();
  await new Promise(setTimeout);
  STYLE.sheet.deleteRule(index);
}

function addButtons(parent, thumb) {
  const elems = [];
  const shown = {};
  for (const item of getProp(thumb, 'data.menu.menuRenderer.items') || []) {
    const menu = getProp(item, 'menuServiceItemRenderer');
    const type = ((menu || {}).icon || {}).iconType;
    let data = COMMANDS[type]; if (!data) continue;
    let {el} = data;
    if (!el) {
      data = COMMANDS[type] = {block: data};
      el = data.el = document.createElement('div');
      el.className = ME;
      el.dataset.block = data.block;
    }
    el.title = ((menu.text || {}).runs || []).map(r => r.text).join('') || data.text;
    el[ME] = thumb;
    shown[type] = 1;
    if (el.parentElement !== parent)
      elems.push(el);
  }
  for (let v in COMMANDS) {
    if (!shown[v] && (v = COMMANDS[v].el))
      v.remove();
  }
  parent.append(...elems);
  if (!STYLE) initStyle();
}

function getInlineState(e) {
  if (e.target.matches(':hover') && !$(PREVIEW_TAG).getBoundingClientRect().width) {
    onHover(e, true);
  }
}

function getProp(obj, path, isRaw) {
  if (!obj) return;
  obj = (obj = obj.wrappedJSObject || obj).polymerController || obj.inst || obj;
  obj = !isRaw && obj.__data || obj;
  for (const p of path.split('.'))
    if (obj) obj = obj[p]; else return;
  return obj;
}

function getStorage() {
  try {
    return JSON.parse(localStorage[GM_info.script.name]);
  } catch (e) {}
}

function isMenuReady(menu) {
  return menu.style.cssText.includes('px;');
}

function $(sel, base = document) {
  return base.querySelector(sel);
}

function timedPromise(promiseInit, ms = 1000) {
  ms = new Promise(resolve => setTimeout(resolve, ms));
  return promiseInit
    ? Promise.race([ms, new Promise(promiseInit)])
    : ms;
}

async function waitFor(sel, base = document) {
  return $(sel, base) || timedPromise(resolve => {
    new MutationObserver((_, o) => {
      const el = $(sel, base); if (!el) return;
      o.disconnect();
      resolve(el);
    }).observe(base, {childList: true, subtree: true});
  });
}

function initStyle() {
  STYLE = document.createElement('style');
  STYLE.textContent = /*language=CSS*/ `
${PREVIEW_PARENT} .${ME} {
  opacity: .5;
}
${PREVIEW_PARENT} .${ME},
${THUMB2_TAG}:hover .${ME},
${THUMB_TAG}:hover .${ME} {
  display: block;
}
.${ME} {
  display: none;
  position: absolute;
  width: 16px;
  height: 16px;
  border-radius: 100%;
  border: 2px solid #fff;
  right: 8px;
  background: #0006;
  box-shadow: .5px .5px 7px #000;
  cursor: pointer;
  opacity: .75;
  z-index: 11000;
}
${PREVIEW_PARENT} .${ME}:hover,
.${ME}:hover {
  opacity: 1;
}
.${ME}:active {
  color: yellow;
}
.${ME}[data-block] { top: 70px; }
.${ME}[data-block="channel"] { top: 100px; }
${PREVIEW_TAG} .${ME}[data-block] { top: 50px; }
${PREVIEW_TAG} .${ME}[data-block="channel"] { top: 80px; }
.ytd-playlist-panel-video-renderer .${ME}[data-block="unwatch"],
.ytd-playlist-video-renderer .${ME}[data-block="unwatch"] {
  top: 15px;
}
ytd-compact-video-renderer .${ME}[data-block] {
  top: 57px;
  right: 7px;
  box-shadow: .5px .5px 4px 6px #000;
  background: #000;
}
ytd-compact-video-renderer .${ME}[data-block="channel"] {
  top: 81px;
}
ytd-compact-video-renderer ytd-thumbnail-overlay-toggle-button-renderer:nth-child(1) {
  top: -4px;
}
ytd-compact-video-renderer ytd-thumbnail-overlay-toggle-button-renderer:nth-child(2) {
  top: 24px;
}
.${ME}::before {
  position: absolute;
  content: '';
  top: -8px;
  left: -6px;
  width: 32px;
  height: 30px;
}
.${ME}::after {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  height: 0;
  margin: auto;
  border: none;
  border-bottom: 2px solid #fff;
}
.${ME}[data-block="video"]::after {
  transform: rotate(45deg);
}
.${ME}[data-block="channel"]::after {
  margin: auto 3px;
}
`.replace(/;/g, '!important;');
  document.head.appendChild(STYLE);
}