// ==UserScript==
// @name YouTube Quick Actions (Hide, Not Interested, Don’t Recommend, Save to Playlist)
// @description Adds quick-action buttons like Hide, Save to Playlist, Not Interested, and Don’t Recommend
// @version 2.0.1.0
// @match https://www.youtube.com/*
// @license Unlicense
// @icon https://www.youtube.com/s/desktop/c722ba88/img/logos/favicon_144x144.png
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @run-at document-start
// @compatible firefox
// @require https://cdnjs.cloudflare.com/ajax/libs/loglevel/1.9.2/loglevel.min.js
// @namespace https://greatest.deepsurf.us/users/1223791
// ==/UserScript==
(function ()
{
"use strict";
console.log("🫡 [Youtube Quick Actions] Script initialized");
const css = String.raw;
const style = css`
:root {
--color-primary: rgba(252, 146, 205, 1);
--color-secondary: rgba(33, 225, 255, 1) ;
}
#quick-actions {
position: absolute;
display: none;
flex-direction: column;
gap: 0.2rem;
align-items: flex-start;
}
#quick-actions.location-01 {
top: 0.5rem;
left: 0.5rem;
}
#quick-actions.location-02 {
top: 0.4rem;
left: 0.4rem;
}
#quick-actions.location-01 .qa-button {
width: 3rem;
}
#quick-actions.location-02 .qa-button {
width: 2.6rem;
}
#quick-actions .qa-button {
background-color: rgba(0, 0, 0, 0.8);
z-index: 1000;
border: 1px solid #f0f0f01c;
height: auto;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 1rem;
font-weight: bold;
border-radius: 4px;
cursor: pointer;
flex-shrink: unset;
padding: 0.5rem;
pointer-events: auto !important;
}
#quick-actions .qa-button.circle {
border-radius: 50%;
}
#quick-actions .qa-button:hover {
border: 1px solid rgba(255, 255, 255, 0.09);
background-color: rgba(0, 0, 0, 1);
}
#quick-actions .qa-icon {
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
}
:is(
ytd-grid-video-renderer,
ytd-video-renderer,
ytd-rich-item-renderer,
yt-lockup-view-model,
ytm-shorts-lockup-view-model-v2,
ytd-compact-video-renderer,
ytd-rich-grid-media,
ytm-shorts-lockup-view-model,
ytd-playlist-video-renderer):has(.yt-spec-button-shape-next--enable-backdrop-filter-experiment:not([aria-label="Notify me"])) .qa-button.frosted {
background-color: rgba(0, 0, 0, 0.3) !important;
backdrop-filter: blur(4px);
box-shadow: 0px 0px 1px 0px rgba(255, 255, 255, 0.1);
border: 0px solid #ffffff;
}
:is(
ytd-grid-video-renderer,
ytd-video-renderer,
ytd-rich-item-renderer,
yt-lockup-view-model,
ytm-shorts-lockup-view-model-v2,
ytd-compact-video-renderer,
ytd-rich-grid-media,
ytm-shorts-lockup-view-model,
ytd-playlist-video-renderer):has(.yt-spec-button-shape-next--enable-backdrop-filter-experiment:not([aria-label="Notify me"])) .qa-button.frosted:hover {
opacity: 0.9;
background: rgba(40, 40, 40, 0.6);
border: 0px solid #ffffff;
}
:is(ytd-grid-video-renderer,
ytd-video-renderer,
ytd-rich-item-renderer,
yt-lockup-view-model,
ytm-shorts-lockup-view-model-v2,
ytd-compact-video-renderer,
ytd-rich-grid-media,
ytm-shorts-lockup-view-model,
ytd-playlist-video-renderer):hover:has(ytd-menu-renderer, button-view-model, .yt-spec-button-shape-next__icon):not([is-dismissed]):not(:has(ytd-rich-grid-media[is-dismissed])):not(:has(.ytDismissibleItemReplacedContent)) #quick-actions {
display: flex;
}
:is(yt-lockup-view-model,
ytd-playlist-video-renderer):hover:has(#quick-actions) {
position: relative;
}
.fancy {
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-image: linear-gradient(
45deg,
var(--color-primary) 17%,
var(--color-secondary) 100%
);
background-size: 400% auto;
background-position: 0% 50%;
animation: animate-gradient 12s linear infinite;
font-weight: bold!important;
letter-spacing: .1rem;
}
@keyframes animate-gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
`;
GM_addStyle(style);
/* -------------------------------------------------------------------------- */
/* Variables */
/* -------------------------------------------------------------------------- */
//Selectors to probe for prop
const tags = ["ytd-video-renderer",
"ytd-rich-item-renderer",
"ytm-shorts-lockup-view-model-v2",
"yt-lockup-view-model",
"ytd-playlist-video-renderer",
"ytd-grid-video-renderer",
"ytd-compact-movie-renderer",
"ytd-compact-video-renderer",
"ytd-rich-grid-media",
"ytm-shorts-lockup-view-model"];
//"YTD-MEMBERSHIP-BADGE-RENDERER";
//Available menu items obj from prop
const menu_items_from_props = [
"menu.menuRenderer.items",
"content.listViewModel.listItems"
];
//Individual menu items from obj
const menu_items_1 = "listItemViewModel?.title?.content";
const menu_items_2 = "menuServiceItemRenderer?.text?.runs?.[0]?.text";
const menu_items_3 = "menuNavigationItemRenderer?.text?.runs?.[0]?.text";
//Thumbnail size
const thumbnail_elem_selector = "img.ytCoreImageHost";
//Action Button selectors
const action_button_selectors = ["button-view-model", ".shortsLockupViewModelHostOutsideMetadataMenu", "yt-icon-button"];
//Dropdown Menu
const dropdown_menu_tag_name = "TP-YT-IRON-DROPDOWN";
//Dropdown Menu Items
const popup_menu_items_selector = "yt-formatted-string.style-scope.ytd-menu-service-item-renderer, yt-list-item-view-model[role='menuitem'], yt-formatted-string.ytd-menu-navigation-item-renderer";
// YT Event Names
const yt_update_action_names = [
"ytd-update-grid-state-action",
"ytd-rich-item-index-update-action"
];
// Icons by Remix Icon (c) Remix Design Licensed under Apache 2.0 http://www.apache.org/licenses/LICENSE-2.0 - https://github.com/Remix-Design/remixicon/blob/master/License
const icon_not_interested = `<svg class="qa-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM7 17C7 14.2386 9.23858 12 12 12C14.7614 12 17 14.2386 17 17H15C15 15.3431 13.6569 14 12 14C10.3431 14 9 15.3431 9 17H7ZM8 11C7.17157 11 6.5 10.3284 6.5 9.5C6.5 8.67157 7.17157 8 8 8C8.82843 8 9.5 8.67157 9.5 9.5C9.5 10.3284 8.82843 11 8 11ZM16 11C15.1716 11 14.5 10.3284 14.5 9.5C14.5 8.67157 15.1716 8 16 8C16.8284 8 17.5 8.67157 17.5 9.5C17.5 10.3284 16.8284 11 16 11Z"></path></svg>`;
const icon_hide = `<svg class="qa-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M17.8827 19.2968C16.1814 20.3755 14.1638 21.0002 12.0003 21.0002C6.60812 21.0002 2.12215 17.1204 1.18164 12.0002C1.61832 9.62282 2.81932 7.5129 4.52047 5.93457L1.39366 2.80777L2.80788 1.39355L22.6069 21.1925L21.1927 22.6068L17.8827 19.2968ZM5.9356 7.3497C4.60673 8.56015 3.6378 10.1672 3.22278 12.0002C4.14022 16.0521 7.7646 19.0002 12.0003 19.0002C13.5997 19.0002 15.112 18.5798 16.4243 17.8384L14.396 15.8101C13.7023 16.2472 12.8808 16.5002 12.0003 16.5002C9.51498 16.5002 7.50026 14.4854 7.50026 12.0002C7.50026 11.1196 7.75317 10.2981 8.19031 9.60442L5.9356 7.3497ZM12.9139 14.328L9.67246 11.0866C9.5613 11.3696 9.50026 11.6777 9.50026 12.0002C9.50026 13.3809 10.6196 14.5002 12.0003 14.5002C12.3227 14.5002 12.6309 14.4391 12.9139 14.328ZM20.8068 16.5925L19.376 15.1617C20.0319 14.2268 20.5154 13.1586 20.7777 12.0002C19.8603 7.94818 16.2359 5.00016 12.0003 5.00016C11.1544 5.00016 10.3329 5.11773 9.55249 5.33818L7.97446 3.76015C9.22127 3.26959 10.5793 3.00016 12.0003 3.00016C17.3924 3.00016 21.8784 6.87992 22.8189 12.0002C22.5067 13.6998 21.8038 15.2628 20.8068 16.5925ZM11.7229 7.50857C11.8146 7.50299 11.9071 7.50016 12.0003 7.50016C14.4855 7.50016 16.5003 9.51488 16.5003 12.0002C16.5003 12.0933 16.4974 12.1858 16.4919 12.2775L11.7229 7.50857Z"></path></svg>`;
const icon_save = `<svg class="qa-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M2 18H12V20H2V18ZM2 11H22V13H2V11ZM2 4H22V6H2V4ZM18 18V15H20V18H23V20H20V23H18V20H15V18H18Z"></path></svg>`;
const icon_dont_recommend = `<svg class="qa-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M14 14.252V16.3414C13.3744 16.1203 12.7013 16 12 16C8.68629 16 6 18.6863 6 22H4C4 17.5817 7.58172 14 12 14C12.6906 14 13.3608 14.0875 14 14.252ZM12 13C8.685 13 6 10.315 6 7C6 3.685 8.685 1 12 1C15.315 1 18 3.685 18 7C18 10.315 15.315 13 12 13ZM12 11C14.21 11 16 9.21 16 7C16 4.79 14.21 3 12 3C9.79 3 8 4.79 8 7C8 9.21 9.79 11 12 11ZM19 17.5858L21.1213 15.4645L22.5355 16.8787L20.4142 19L22.5355 21.1213L21.1213 22.5355L19 20.4142L16.8787 22.5355L15.4645 21.1213L17.5858 19L15.4645 16.8787L16.8787 15.4645L19 17.5858Z"></path></svg>`;
const downloadIcon = `<svg class="qa-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M13 10H18L12 16L6 10H11V3H13V10ZM4 19H20V12H22V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V12H4V19Z"></path></svg>`;
const icon_trash = `<svg class="qa-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M17 6H22V8H20V21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V8H2V6H7V3C7 2.44772 7.44772 2 8 2H16C16.5523 2 17 2.44772 17 3V6ZM18 8H6V20H18V8ZM9 11H11V17H9V11ZM13 11H15V17H13V11ZM9 4V6H15V4H9Z"></path></svg>`;
/* -------------------------------------------------------------------------- */
/* Functions */
/* -------------------------------------------------------------------------- */
/* ----------------------------- Menu Commmands ----------------------------- */
let is_logging_enabled = GM_getValue("is_logging_enabled", false);
let use_frosted = GM_getValue("use_frosted", true);
let use_circle = GM_getValue("use_circle", false);
const menu_commands = [
{
label: () => `Frosted Button: ${use_frosted ? "✅ ON" : "❌ OFF"}`,
toggle: () =>
{
use_frosted = !use_frosted;
GM_setValue("use_frosted", use_frosted);
window.location.reload(true);
}
},
{
label: () => `Circle Button: ${use_circle ? "✅ ON" : "❌ OFF"}`,
toggle: () =>
{
use_circle = !use_circle;
GM_setValue("use_circle", use_circle);
window.location.reload(true);
}
},
{
label: () => `Logs: ${is_logging_enabled ? "✅ ON" : "❌ OFF"}`,
toggle: () =>
{
is_logging_enabled = !is_logging_enabled;
GM_setValue("is_logging_enabled", is_logging_enabled);
window.location.reload(true);
}
}
];
function registermenu_commands()
{
for (const command of menu_commands)
{
GM_registerMenuCommand(command.label(), command.toggle);
}
}
registermenu_commands();
is_logging_enabled ? log.enableAll() : log.disableAll();
/* ---------------------------- Menu Commands End --------------------------- */
function append_buttons(element, menulist_items, position)
{
const final_menulist_items = [...new Set(menulist_items)];
const buttons_to_append = [];
for (const item of final_menulist_items)
{
if (!item) continue;
let class_name;
let title_text;
let icon;
if (item.startsWith("Remove from "))
{
class_name = "remove";
title_text = "Remove from playlist";
icon = icon_trash;
} else
{
switch (item)
{
case "Not interested":
class_name = "not_interested";
title_text = "Not interested";
icon = icon_not_interested;
break;
case "Don't recommend channel":
class_name = "dont_recommend_channel";
title_text = "Don't recommend channel";
icon = icon_dont_recommend;
break;
case "Hide":
class_name = "hide";
title_text = "Hide video";
icon = icon_hide;
break;
case "Save to playlist":
class_name = "save";
title_text = "Save to playlist";
icon = icon_save;
break;
default:
continue;
}
}
const set_frosted = use_frosted ? " frosted" : "";
const set_circle = use_circle ? " circle" : "";
buttons_to_append.push(
`<button class="qa-button ${set_frosted} ${set_circle} ${class_name}" data-icon="${class_name}" title="${title_text}" data-text="${title_text}">${icon}</button>`,
);
}
const buttons_container = document.createElement("div");
buttons_container.id = "quick-actions";
buttons_container.classList.add(position);
buttons_container.innerHTML = buttons_to_append.join("");
if (!element.querySelector("#quick-actions"))
{
element.insertAdjacentElement("beforeend", buttons_container);
}
}
function get_prop(target, path)
{
try
{
return new Function('object', `return object.${path}`)(target) ?? [];
} catch
{
return [];
}
}
function get_menu_list(target)
{
if (!target) return;
return target.map(item =>
{
const paths = [menu_items_1, menu_items_2, menu_items_3];
for (const path of paths)
{
const result = get_prop(item, path);
if (result.length) return result;
}
return null;
}).filter(Boolean);
}
function enable_fallback_tooltip(selector)
{
let active_tooltip = null;
let current_elem = null;
const create_tooltip = (text) =>
{
const tooltip = document.createElement("div");
tooltip.id = "quick-action-tooltip";
Object.assign(tooltip.style, {
position: "fixed",
zIndex: "999999",
background: "rgba(0,0,0,0.8)",
color: "#fff",
padding: ".6rem 1rem",
borderRadius: ".8rem",
fontSize: "1.5rem",
pointerEvents: "none"
});
tooltip.textContent = text;
document.body.appendChild(tooltip);
return tooltip;
};
document.addEventListener("pointerenter", event =>
{
const target_elem = event.target.closest(selector);
if (!target_elem) return;
if (current_elem === target_elem) return;
document.querySelectorAll("#quick-action-tooltip").forEach((element) => element.remove());
const tooltip_text = target_elem.getAttribute("title") || target_elem.getAttribute("data-text");
if (!tooltip_text) return;
if (target_elem.hasAttribute("title"))
{
target_elem.setAttribute("data-original-title", tooltip_text);
target_elem.removeAttribute("title");
}
current_elem = target_elem;
active_tooltip = create_tooltip(tooltip_text);
const update_position = e =>
{
if (!active_tooltip) return;
active_tooltip.style.left = e.clientX + 8 + "px";
active_tooltip.style.top = e.clientY + 8 + "px";
};
current_elem._tooltip_move = update_position;
document.addEventListener("pointermove", update_position);
}, true);
document.addEventListener("pointerleave", event =>
{
const left_elem = event.target.closest(selector);
if (!left_elem) return;
if (event.relatedTarget && left_elem.contains(event.relatedTarget))
{
return;
}
if (active_tooltip)
{
document.removeEventListener("pointermove", left_elem._tooltip_move);
active_tooltip.remove();
active_tooltip = null;
delete left_elem._tooltip_move;
}
if (left_elem.hasAttribute("data-original-title"))
{
left_elem.setAttribute("title", left_elem.getAttribute("data-original-title"));
left_elem.removeAttribute("data-original-title");
}
current_elem = null;
}, true);
};
function get_value_by_path(obj, path)
{
return path.split(".").reduce((acc, key) =>
{
if (acc && typeof acc === "object" && key in acc)
{
return acc[key];
}
return undefined;
}, obj);
}
function find_target_path_value(obj)
{
if (obj == null || typeof obj !== "object") return null;
for (const path of menu_items_from_props)
{
const value = get_value_by_path(obj, path);
if (Array.isArray(value))
{
log.log(`🎉 Menu Obj Path Used: ${path}`);
return value;
}
}
for (const key in obj)
{
if (obj[key] && typeof obj[key] === "object")
{
const found = find_target_path_value(obj[key]);
if (found !== null) return found;
}
}
return null;
}
function find_first_menu_or_list_value(root_element = document)
{
const elements = root_element.querySelectorAll("*");
for (const element of elements)
{
if (element.data && typeof element.data === "object")
{
const found_value = find_target_path_value(element.data);
if (found_value !== null)
{
return found_value;
}
}
}
return null;
}
function find_elem_in_parent_dom_tree(origin_elem, tag_names)
{
let node = origin_elem;
let depth = 0;
while (node && depth <= 3)
{
const button = node.querySelector(tag_names.join(','));
if (button)
{
return button;
}
node = node.parentElement;
depth++;
}
return null;
}
async function wait_until(condition_func, { interval = 100, timeout = 3000 } = {})
{
const start_time = Date.now();
while (Date.now() - start_time < timeout)
{
const result = condition_func();
if (result) return result;
await new Promise((resolve) => setTimeout(resolve, interval));
}
throw new Error("⏰ Timeout: Target element is not visible in time");
}
function retry_click(element, { max_attempts = 5, interval = 1000 } = {})
{
return new Promise((resolve) =>
{
let attempts = 0;
function try_click()
{
if (!element || attempts >= max_attempts)
{
log.log("⚠️ Retry failed or element missing.");
return resolve();
}
const rect = element.getBoundingClientRect();
const is_visible = rect.width > 0 && rect.height > 0;
if (is_visible)
{
element.dispatchEvent(
new MouseEvent("click", {
view: document.defaultView,
bubbles: true,
cancelable: true,
}),
);
log.log("👇 Clicked matching menu item");
return resolve();
} else
{
attempts++;
log.log("👇 Clicking attempt: ", attempts);
setTimeout(try_click, interval);
}
}
try_click();
});
}
function get_visible_elem(target_selector, element = document.body)
{
const elements = element.querySelectorAll(target_selector);
if (!elements || elements.length === 0)
{
return null;
}
for (const element of elements)
{
const rect = element.getBoundingClientRect();
const has_dimensions_and_in_view = rect.width > 0 &&
rect.height > 0 &&
rect.bottom > 0 &&
rect.right > 0 &&
rect.top < (window.innerHeight || element.documentElement.clientHeight) &&
rect.left < (window.innerWidth || element.documentElement.clientWidth);
if (!has_dimensions_and_in_view)
{
continue;
}
const computedStyle = window.getComputedStyle(element);
const is_visible = computedStyle.opacity !== '0' &&
computedStyle.visibility !== 'hidden' &&
computedStyle.display !== 'none';
if (!is_visible)
{
continue;
}
log.log("👀 Found visible element:", element);
return element;
}
log.log("⚠️ No visible menu found.");
return null;
}
/* -------------------------------------------------------------------------- */
/* Listeners */
/* -------------------------------------------------------------------------- */
document.addEventListener("mouseover", (event) =>
{
const path = event.composedPath();
for (let element of path)
{
const tag = element?.tagName?.toLowerCase();
if (tag && !element.querySelector("#quick-actions") && tags.includes(tag))
{
log.log("⭐ Video Elem: ", element.tagName, element);
const data = find_first_menu_or_list_value(element);
if (!data)
{
log.log("⚠️ No props data found.");
//NOTE - Observe
continue;
}
log.log("🎥 Video Menu Props: ", data);
const thumbnail_elem = element.querySelector(thumbnail_elem_selector);
const thumbnail_size =
thumbnail_elem?.getClientRects?.().length > 0
? parseInt(thumbnail_elem.getClientRects()[0].width)
: 100;
log.log("🖼️ Thumbnail Size: ", thumbnail_size);
const container_position = thumbnail_size < 211 ? "location-02" : "location-01";
const menulist_items = get_menu_list(data);
log.log("📃 Menu items: ", menulist_items);
append_buttons(element, menulist_items, container_position);
} else
{
continue;
}
}
}, true);
document.addEventListener("click", async function (event)
{
document.querySelectorAll("#quick-action-tooltip").forEach((element) => element.remove());
const button = event.target.closest(".qa-button");
if (!button) return;
event.stopPropagation();
event.stopImmediatePropagation();
event.preventDefault();
const actionType = button.dataset.icon;
let response;
switch (actionType)
{
case "not_interested":
response = "Not interested";
log.log("😴 Marking as not interested");
break;
case "dont_recommend_channel":
response = "Don't recommend channel";
log.log("🚫 Don't recommend channel");
break;
case "hide":
response = "Hide";
log.log("🗑️ Hiding video");
break;
case "remove":
response = "Remove from";
log.log("🗑️ Remove from playlist");
break;
case "save":
response = "Save to playlist";
log.log("📂 Saving to playlist");
break;
default:
log.log("☠️ Unknown action");
}
const menus = find_elem_in_parent_dom_tree(
button,
action_button_selectors
);
if (!menus)
{
log.log("❌ Menu button not found.");
return;
}
log.log("🎯 Menu button found:", menus);
if (menus.id.toLowerCase() === "button")
{
menus.dispatchEvent(new MouseEvent("click", { bubbles: false }));
} else
{
menus.querySelector("button").dispatchEvent(new MouseEvent("click", { bubbles: false }));
}
log.log("👇 Button clicked, waiting for menu...");
try
{
const visible_menu = await wait_until(() => get_visible_elem(dropdown_menu_tag_name), {
interval: 100,
timeout: 3000,
});
if (visible_menu)
{
try
{
const targetItem = await wait_until(
() =>
{
const items = visible_menu.querySelectorAll(popup_menu_items_selector);
return items.length > 0 ? items : null;
},
{
interval: 100,
timeout: 5000,
},
);
if (targetItem)
{
log.log("🎉 Target items found:", targetItem);
for (const item of targetItem)
{
if (
item.textContent === response ||
(response === "Remove from" && item.textContent.startsWith("Remove from"))
)
{
log.log(`✅ Matched: (${response} = ${item.textContent})`);
log.log(`✅`, item);
const button = item;
await retry_click(button, { max_attempts: 3, interval: 1000 }).finally(() =>
{
document.body.click();
});
break;
} else
{
log.log(`❌ Not a match: (${response} = ${item.textContent})`);
}
}
}
} catch (error)
{
log.log("🛑 !", error.message);
}
}
} catch (error)
{
log.log("🛑 !!", error.message);
}
});
document.addEventListener("yt-action", (event) =>
{
if (yt_update_action_names.includes(event.detail.actionName))
{
log.log("🐛 Page updated.");
document.querySelectorAll("#quick-actions").forEach((element) => element.remove());
}
});
enable_fallback_tooltip(".qa-button");
/* -------------------------------------------------------------------------- */
/* This script is brought to you in support of FIFTY FIFTY 💖 */
/* -------------------------------------------------------------------------- */
if ("💖")
{
const selectors_to_watch = ['a', 'yt-formatted-string', '.yt-core-attributed-string'];
const observed_elements = new WeakMap();
const target_texts = [
"FIFTY FIFTY Official",
"FIFTY FIFTY",
"@WE_FIFTYFIFTY"
];
function has_matching_text(element)
{
const text = element.textContent.trim();
return target_texts.includes(text);
}
function observe_text_content_changes(element)
{
if (observed_elements.has(element)) return;
const element_observer = new MutationObserver(() =>
{
element.classList.toggle("fancy", has_matching_text(element));
});
observed_elements.set(element, element_observer);
element_observer.observe(element, { characterData: true, childList: true, subtree: true });
}
function handle_removed_node(node)
{
if (node.nodeType !== 1) return;
if (observed_elements.has(node))
{
observed_elements.get(node).disconnect();
observed_elements.delete(node);
}
node.querySelectorAll(selectors_to_watch.join(',')).forEach(child =>
{
if (observed_elements.has(child))
{
observed_elements.get(child).disconnect();
observed_elements.delete(child);
}
});
}
function init()
{
document.querySelectorAll(selectors_to_watch.join(',')).forEach(element =>
{
if (has_matching_text(element)) element.classList.add("fancy");
observe_text_content_changes(element);
});
const observer = new MutationObserver(mutations =>
{
for (const mutation of mutations)
{
for (const node of mutation.addedNodes)
{
if (node.nodeType !== 1) continue;
for (const selector of selectors_to_watch)
{
const elements = node.matches(selector) ? [node] : node.querySelectorAll(selector);
elements.forEach(element =>
{
if (has_matching_text(element)) element.classList.add("fancy");
observe_text_content_changes(element);
});
}
}
for (const node of mutation.removedNodes)
{
handle_removed_node(node);
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
if (document.body)
{
init();
} else
{
new MutationObserver((_, obs) =>
{
if (document.body)
{
obs.disconnect();
init();
}
}).observe(document.documentElement, { childList: true });
}
}
})();