import { isOnInvidious, parseYouTubeVideoIDFromURL } from "@ajayyy/maze-utils/lib/video"; import Config from "../config"; import { getVideoLabel } from "./videoLabels"; import { setThumbnailListener } from "@ajayyy/maze-utils/lib/thumbnailManagement"; export async function labelThumbnails(thumbnails: HTMLImageElement[]): Promise { await Promise.all(thumbnails.map((t) => labelThumbnail(t))); } export async function labelThumbnail(thumbnail: HTMLImageElement): Promise { if (!Config.config?.fullVideoSegments || !Config.config?.fullVideoLabelsOnThumbnails) { hideThumbnailLabel(thumbnail); return null; } const link = (isOnInvidious() ? thumbnail.parentElement : thumbnail.querySelector("#thumbnail")) as HTMLAnchorElement if (!link || link.nodeName !== "A" || !link.href) return null; // no link found const videoID = parseYouTubeVideoIDFromURL(link.href)?.videoID; if (!videoID) { hideThumbnailLabel(thumbnail); return null; } const category = await getVideoLabel(videoID); if (!category) { hideThumbnailLabel(thumbnail); return null; } const { overlay, text } = createOrGetThumbnail(thumbnail); overlay.style.setProperty('--category-color', `var(--sb-category-preview-${category}, var(--sb-category-${category}))`); overlay.style.setProperty('--category-text-color', `var(--sb-category-text-preview-${category}, var(--sb-category-text-${category}))`); text.innerText = chrome.i18n.getMessage(`category_${category}`); overlay.classList.add("sponsorThumbnailLabelVisible"); return overlay; } function getOldThumbnailLabel(thumbnail: HTMLImageElement): HTMLElement | null { return thumbnail.querySelector(".sponsorThumbnailLabel") as HTMLElement | null; } function hideThumbnailLabel(thumbnail: HTMLImageElement): void { const oldLabel = getOldThumbnailLabel(thumbnail); if (oldLabel) { oldLabel.classList.remove("sponsorThumbnailLabelVisible"); } } function createOrGetThumbnail(thumbnail: HTMLImageElement): { overlay: HTMLElement; text: HTMLElement } { const oldElement = getOldThumbnailLabel(thumbnail); if (oldElement) { return { overlay: oldElement as HTMLElement, text: oldElement.querySelector("span") as HTMLElement }; } const overlay = document.createElement("div") as HTMLElement; overlay.classList.add("sponsorThumbnailLabel"); // Disable hover autoplay overlay.addEventListener("pointerenter", (e) => { e.stopPropagation(); thumbnail.dispatchEvent(new PointerEvent("pointerleave", { bubbles: true })); }); overlay.addEventListener("pointerleave", (e) => { e.stopPropagation(); thumbnail.dispatchEvent(new PointerEvent("pointerenter", { bubbles: true })); }); const icon = createSBIconElement(); const text = document.createElement("span"); overlay.appendChild(icon); overlay.appendChild(text); thumbnail.appendChild(overlay); return { overlay, text }; } function createSBIconElement(): SVGSVGElement { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("viewBox", "0 0 565.15 568"); const use = document.createElementNS("http://www.w3.org/2000/svg", "use"); use.setAttribute("href", "#SponsorBlockIcon"); svg.appendChild(use); return svg; } // Inserts the icon svg definition, so it can be used elsewhere function insertSBIconDefinition() { const container = document.createElement("span"); // svg from /public/icons/PlayerStartIconSponsorBlocker.svg, with useless stuff removed container.innerHTML = ` `; document.body.appendChild(container.children[0]); } export function setupThumbnailListener(): void { setThumbnailListener(labelThumbnails, () => { insertSBIconDefinition(); }, () => Config.isReady()); }