Skip to content

Instantly share code, notes, and snippets.

@azdanov
Created February 26, 2025 19:26
Show Gist options
  • Select an option

  • Save azdanov/42661b54de30cb31448ca64d59361ee7 to your computer and use it in GitHub Desktop.

Select an option

Save azdanov/42661b54de30cb31448ca64d59361ee7 to your computer and use it in GitHub Desktop.
Hover a thumbnail on YouTube to quickly mark "Not interested" or "Don't recommend channel"
// ==UserScript==
// @name YT: not interested in one click
// @description Hover a thumbnail on YouTube to quickly mark "Not interested" or "Don't recommend channel"
// @version 1.4.0
//
// @match https://www.youtube.com/*
//
// @noframes
// @grant none
//
// @author wOxxOm
// @namespace wOxxOm.scripts
// @license MIT License
// @downloadURL https://update.greasyfork.org/scripts/396936/YT%3A%20not%20interested%20in%20one%20click.user.js
// @updateURL https://update.greasyfork.org/scripts/396936/YT%3A%20not%20interested%20in%20one%20click.meta.js
// ==/UserScript==
'use strict';
/**
* YouTube One-Click Dismiss
* Adds buttons to quickly dismiss videos or channels on YouTube
*/
class YouTubeOneClickDismiss {
constructor() {
// Constants
this.THUMB2 = 'yt-thumbnail-view-model';
this.ME = 'yt-one-click-dismiss';
this.COMMANDS = {
NOT_INTERESTED: 'video',
REMOVE: 'channel',
DELETE: 'unwatch',
};
// Load config from storage or use defaults
const config = this.getStorage() || {};
this.THUMB = config.THUMB || `${this.THUMB2},ytd-thumbnail,ytd-playlist-thumbnail`;
this.PREVIEW_TAG = config.PREVIEW_TAG || 'ytd-video-preview';
this.PREVIEW_PARENT = config.PREVIEW_PARENT || '#media-container';
this.ENTRY = config.ENTRY || [
'ytd-rich-item-renderer', // home
'ytd-compact-video-renderer', // watch (recommendations)
'ytd-playlist-video-renderer', // watch later, likes
'ytd-playlist-panel-video-renderer', // playlist
'ytd-video-renderer', // history
].join(',');
this.MENU1 = config.MENU1 || 'ytd-menu-popup-renderer';
this.MENU2 = config.MENU2 || 'yt-sheet-view-model';
this.MENU_BTN = config.MENU_BTN || '.dropdown-trigger, .yt-lockup-metadata-view-model-wiz__menu-button button';
this.STYLE = null;
this.inlinable = null;
// Initialize
this.init();
}
init() {
// Add event listeners
addEventListener('click', this.onClick.bind(this), true);
addEventListener('mousedown', this.onClick.bind(this), true);
addEventListener('mouseover', this.onHover.bind(this), true);
addEventListener('yt-action', this.onYtAction.bind(this));
}
onYtAction({detail: d}) {
if (d.actionName === 'yt-set-cookie-command') {
this.inlinable = !d.args[0].setCookieCommand.value;
}
}
onHover(evt, delayed) {
const inline = evt.target.closest(this.PREVIEW_TAG);
const el = inline || evt.target.closest(this.THUMB);
const thumb = el && inline === el ? this.$(this.THUMB, inline) : el;
if (!thumb || thumb.getElementsByClassName(this.ME)[0]) {
return;
}
const shouldAddButtons = inline || delayed || this.shouldAddButtonsForThumbnail(evt);
if (shouldAddButtons) {
if (inline) {
this.addButtons(
this.$(this.PREVIEW_PARENT, el),
this.getProp(el, 'mediaRenderer') || this.getProp(el, 'opts.mediaRenderer')
);
} else {
this.addButtons(thumb, thumb);
}
}
}
shouldAddButtonsForThumbnail(evt) {
if (this.inlinable != null) {
return !this.inlinable;
}
const previewEl = this.$(this.PREVIEW_TAG);
this.inlinable = this.getProp(previewEl, 'inlinePreviewIsEnabled');
if (this.inlinable != null) {
return !this.inlinable;
}
setTimeout(() => this.getInlineState(evt), 250);
return false;
}
async onClick(e) {
if (e.button) return;
const me = e.target;
const thumb = me[this.ME];
if (!thumb) return;
const a = me.closest('a');
const upd = thumb.localName === this.THUMB2;
const MENU = upd ? this.MENU2 : this.MENU1;
const POPUPICON = upd
? 'props.data.leadingImage.sources.0.clientResource.imageName'
: 'data.icon.iconType';
e.stopPropagation();
e.stopImmediatePropagation();
e.preventDefault();
if (e.type === 'click') return;
if (a) this.setPointerEvents(a, 'none');
await new Promise(r => me.addEventListener('mouseup', r, {once: true}));
let index, menu, popup, entry, el;
if ((entry = thumb.closest(this.ENTRY)) && (el = this.$(this.MENU_BTN, entry))) {
await Promise.resolve(); // Microtask delay
index = this.STYLE.sheet.insertRule(`${MENU}:not(#\\0) { opacity: 0 !important }`);
el.dispatchEvent(new Event('click'));
if ((popup = await this.waitFor('ytd-popup-container'))) {
menu = await this.waitFor(MENU, popup);
}
}
if (a) setTimeout(() => this.setPointerEvents(a), 0);
if (!menu) {
this.STYLE.sheet.deleteRule(index);
this.handleMenuNotFound(me);
return;
}
if (me.title) me.title = '';
await this.waitForMenuReady(menu);
await new Promise(setTimeout);
el = this.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]);
this.STYLE.sheet.deleteRule(index);
return;
}
try {
this.clickMenuOption(menu, me.dataset.block, upd, POPUPICON);
} catch (e) {
console.error('Error clicking menu option:', e);
}
await new Promise(setTimeout);
document.body.click();
await new Promise(setTimeout);
this.STYLE.sheet.deleteRule(index);
}
async handleMenuNotFound(me) {
const 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 this.timedPromise(null, 5000);
me.title = '';
}
async waitForMenuReady(menu) {
if (!this.isMenuReady(menu)) {
let mo;
const success = await this.timedPromise(resolve => {
mo = new MutationObserver(() => this.isMenuReady(menu) && resolve(true));
mo.observe(menu, {attributes: true, attributeFilter: ['style']});
});
if (!success) console.warn('Timeout waiting for px in `style` of', menu);
mo.disconnect();
}
}
clickMenuOption(menu, blockType, upd, POPUPICON) {
for (const el of this.$('[role=listbox], [role=menu]', menu).children) {
const iconType = this.getProp(el, POPUPICON);
const command = this.COMMANDS[iconType];
if (blockType === (command || {}).block) {
el.click();
break;
}
}
}
addButtons(parent, thumb) {
const upd = thumb.localName === this.THUMB2;
const ITEMS = upd
? 'data.content.lockupViewModel.metadata.lockupMetadataViewModel.menuButton.' +
'buttonViewModel.onTap.innertubeCommand.showSheetCommand.panelLoadingStrategy.' +
'inlineContent.sheetViewModel.content.listViewModel.listItems'
: 'data.menu.menuRenderer.items';
const ITEM = upd ? 'listItemViewModel' : 'menuServiceItemRenderer';
const ICON = upd ? 'leadingImage.sources.0.clientResource.imageName' : 'icon.iconType';
const TEXT = upd ? 'title.content' : 'text.runs.0.text';
const elems = [];
const shown = {};
const items = this.getProp(upd ? thumb.closest(this.ENTRY) : thumb, ITEMS) || [];
for (const item of items) {
const menu = item[ITEM];
const type = this.getProp(menu, ICON);
let data = this.COMMANDS[type];
if (!data) continue;
let {el} = data;
if (!el) {
data = this.COMMANDS[type] = {block: data};
el = data.el = document.createElement('div');
el.className = this.ME;
el.dataset.block = data.block;
}
el.title = this.getProp(menu, TEXT) || data.text;
el[this.ME] = thumb;
shown[type] = 1;
if (el.parentElement !== parent) {
elems.push(el);
}
}
// Remove buttons that shouldn't be shown
for (let v in this.COMMANDS) {
if (!shown[v] && (v = this.COMMANDS[v].el)) {
v.remove();
}
}
if (elems.length) {
requestAnimationFrame(() => parent.append(...elems));
}
if (!this.STYLE) this.initStyle();
}
getInlineState(e) {
if (e.target.matches(':hover') && !this.$(this.PREVIEW_TAG).getBoundingClientRect().width) {
this.onHover(e, true);
}
}
getProp(obj, path, isRaw) {
if (!obj) return;
if (obj instanceof Node) {
obj = (obj = obj.wrappedJSObject || obj).polymerController || obj.__instance || obj.inst || obj;
obj = !isRaw && obj.__data || obj;
}
for (const p of path.split('.')) {
if (obj) obj = obj[p];
else return;
}
return obj;
}
getStorage() {
try {
return JSON.parse(localStorage[GM_info.script.name]);
} catch (e) {
return null;
}
}
isMenuReady(menu) {
return menu.style.cssText.includes('px;');
}
$(sel, base = document) {
return base.querySelector(sel);
}
setPointerEvents(el, value = null) {
if (value != null) {
el.style.setProperty('pointer-events', value, 'important');
} else {
el.style.removeProperty('pointer-events');
}
}
timedPromise(promiseInit, ms = 1000) {
const timeoutPromise = new Promise(resolve => setTimeout(resolve, ms));
return promiseInit
? Promise.race([timeoutPromise, new Promise(promiseInit)])
: timeoutPromise;
}
async waitFor(sel, base = document) {
const existingElement = this.$(sel, base);
if (existingElement) return existingElement;
return this.timedPromise(resolve => {
const observer = new MutationObserver((_, o) => {
const el = this.$(sel, base);
if (!el) return;
o.disconnect();
resolve(el);
});
observer.observe(base, {childList: true, subtree: true});
});
}
initStyle() {
this.STYLE = document.createElement('style');
this.STYLE.textContent = /*language=CSS*/ `
${this.PREVIEW_PARENT} .${this.ME} {
opacity: .5;
}
${this.PREVIEW_PARENT} .${this.ME},
:is(${this.THUMB}):hover .${this.ME},
:is(${this.THUMB}):hover ~ .${this.ME} {
display: block;
}
.${this.ME} {
display: none;
position: absolute;
width: 16px;
height: 16px;
border-radius: 100%;
border: 2px solid #fff;
right: 8px;
margin: 0;
padding: 0;
background: #0006;
box-shadow: .5px .5px 7px #000;
pointer-events: auto;
cursor: pointer;
opacity: .75;
z-index: 11000;
}
${this.PREVIEW_PARENT} .${this.ME}:hover,
.${this.ME}:hover {
opacity: 1;
}
.${this.ME}:active {
color: yellow;
}
.${this.ME}[data-block] { top: 75px; }
.${this.ME}[data-block="channel"] { top: 105px; }
yt-thumbnail-view-model .${this.ME} { margin-top: 10px; }
${this.PREVIEW_TAG} .${this.ME}[data-block] { right: 18px; margin-top: 24px; }
.ytd-playlist-panel-video-renderer .${this.ME}[data-block="unwatch"],
.ytd-playlist-video-renderer .${this.ME}[data-block="unwatch"] {
top: 15px;
}
ytd-compact-video-renderer .${this.ME}[data-block] {
top: 57px;
right: 7px;
box-shadow: .5px .5px 4px 6px #000;
background: #000;
}
ytd-compact-video-renderer .${this.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;
}
.${this.ME}::before {
position: absolute;
content: '';
top: -8px;
left: -6px;
width: 32px;
height: 30px;
}
.${this.ME}::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
height: 0;
margin: auto;
border: none;
border-bottom: 2px solid #fff;
}
.${this.ME}[data-block="video"]::after {
transform: rotate(45deg);
}
.${this.ME}[data-block="channel"]::after {
margin: auto 3px;
}
`.replace(/;/g, '!important;');
document.head.appendChild(this.STYLE);
}
}
// Initialize the script
new YouTubeOneClickDismiss();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment