- // ==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.3.1
- //
- // @match https://www.youtube.com/*
- //
- // @noframes
- // @grant none
- //
- // @author wOxxOm
- // @namespace wOxxOm.scripts
- // @license MIT License
- // ==/UserScript==
- 'use strict';
-
- const THUMB2 = 'yt-thumbnail-view-model';
- const {
- THUMB = THUMB2 + ',ytd-thumbnail,ytd-playlist-thumbnail',
- PREVIEW_TAG = 'ytd-video-preview',
- PREVIEW_PARENT = '#media-container', // parent for the added buttons; must be visible
- 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(','),
- MENU1 = 'ytd-menu-popup-renderer',
- MENU2 = 'yt-sheet-view-model',
- MENU_BTN = '.dropdown-trigger, .yt-lockup-metadata-view-model-wiz__menu-button button',
- } = getStorage() || {};
- const ME = 'yt-one-click-dismiss';
- const COMMANDS = {
- NOT_INTERESTED: 'video',
- REMOVE: 'channel',
- DELETE: 'unwatch',
- };
- let STYLE;
- let inlinable;
-
- addEventListener('click', onClick, true);
- addEventListener('mousedown', onClick, true);
- addEventListener('mouseover', onHover, true);
- addEventListener('yt-action', ({detail: d}) => {
- if (d.actionName === 'yt-set-cookie-command') {
- inlinable = !d.args[0].setCookieCommand.value;
- }
- });
-
- function onHover(evt, delayed) {
- const inline = evt.target.closest(PREVIEW_TAG);
- const el = inline || evt.target.closest(THUMB);
- const thumb = el && inline === el ? $(THUMB, inline) : el;
- if (thumb && !thumb.getElementsByClassName(ME)[0] && (
- inline ||
- delayed || (
- inlinable != null || (inlinable = getProp($(PREVIEW_TAG), 'inlinePreviewIsEnabled')) != null
- ? !inlinable
- : setTimeout(getInlineState, 250, evt) && 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');
- const upd = thumb.localName === THUMB2;
- const MENU = upd ? MENU2 : 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) setPointerEvents(a, 'none');
- await new Promise(r => me.addEventListener('mouseup', r, {once: true}));
- let index, menu, popup, entry, el;
- if ((entry = thumb.closest(ENTRY)) && (el = $(MENU_BTN, 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 (a) setTimeout(setPointerEvents, 0, a);
- 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], [role=menu]', menu).children) {
- if (me.dataset.block === (COMMANDS[getProp(el, POPUPICON)] || {}).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 upd = thumb.localName === 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 = {};
- for (const item of getProp(upd ? thumb.closest(ENTRY) : thumb, ITEMS) || []) {
- const menu = item[ITEM];
- const type = getProp(menu, ICON);
- 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 = getProp(menu, TEXT) || 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();
- }
- requestAnimationFrame(() => 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;
- 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;
- }
-
- 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 setPointerEvents(el, value) {
- if (value != null) el.style.setProperty('pointer-events', value, 'important');
- else el.style.removeProperty('pointer-events');
- }
-
- 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},
- :is(${THUMB}):hover .${ME},
- :is(${THUMB}):hover ~ .${ME} {
- display: block;
- }
- .${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;
- }
- ${PREVIEW_PARENT} .${ME}:hover,
- .${ME}:hover {
- opacity: 1;
- }
- .${ME}:active {
- color: yellow;
- }
- .${ME}[data-block] { top: 70px; }
- .${ME}[data-block="channel"] { top: 100px; }
- yt-thumbnail-view-model .${ME} { margin-top: 10px; }
- ${PREVIEW_TAG} .${ME}[data-block] { right: 18px; margin-top: 24px; }
- .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);
- }