console.log("[YT-Erfasser] Content Script geladen"); const sentUrls = new Set(); function extractVideoFromCard(element) { const link = element.querySelector('a[href*="/watch?v="]'); if (!link) return null; const match = link.href.match(/[?&]v=([^&]+)/); if (!match) return null; const title = element.querySelector("h3 a")?.textContent?.trim(); if (!title) return null; const thumbnail = element.querySelector('a[href*="/watch?v="] img')?.src; const youtuber = element.querySelector('a[href^="/@"]')?.textContent?.trim() || "Unbekannt"; return { title, youtuber, thumbnail_url: thumbnail || `https://img.youtube.com/vi/${match[1]}/hqdefault.jpg`, youtube_url: `https://www.youtube.com/watch?v=${match[1]}`, }; } function collectVideos(elements) { const videos = []; for (const el of elements) { const video = extractVideoFromCard(el); if (!video) continue; if (sentUrls.has(video.youtube_url)) continue; sentUrls.add(video.youtube_url); videos.push(video); } return videos; } // --- Debounced Batch-Versand --- let pendingVideos = []; let sendTimer = null; function queueVideos(videos) { pendingVideos.push(...videos); if (!sendTimer) { sendTimer = setTimeout(() => { if (pendingVideos.length > 0) { console.log(`[YT-Erfasser] ${pendingVideos.length} Videos senden`); browser.runtime.sendMessage(pendingVideos); } pendingVideos = []; sendTimer = null; }, 250); } } // --- IntersectionObserver: nur sichtbare Cards erfassen --- const visibilityObserver = new IntersectionObserver((entries) => { const cards = entries.filter((e) => e.isIntersecting).map((e) => e.target); if (cards.length > 0) { queueVideos(collectVideos(cards)); } }, { threshold: 0.5 }); function observeCard(el) { visibilityObserver.observe(el); } // --- MutationObserver: neue Cards registrieren --- const mutationObserver = new MutationObserver((mutations) => { for (const mutation of mutations) { for (const node of mutation.addedNodes) { if (node.nodeType !== Node.ELEMENT_NODE) continue; if (node.matches?.("ytd-rich-item-renderer, ytd-video-renderer")) { observeCard(node); } node.querySelectorAll?.("ytd-rich-item-renderer, ytd-video-renderer").forEach(observeCard); } } }); mutationObserver.observe(document.body, { childList: true, subtree: true }); // --- SPA Navigation --- document.addEventListener("yt-navigate-finish", () => { sentUrls.clear(); setTimeout(() => { document.querySelectorAll("ytd-rich-item-renderer, ytd-video-renderer").forEach(observeCard); }, 500); }); // --- Init --- document.querySelectorAll("ytd-rich-item-renderer, ytd-video-renderer").forEach(observeCard);