feat: add content script that collects video ids from YouTube pages
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
c95da940eb
commit
15ac3ba570
@@ -0,0 +1,76 @@
|
||||
(async () => {
|
||||
const { parseVideoLink, pageFromPath } = await import(chrome.runtime.getURL('src/lib/video-id.js'));
|
||||
const { readVidiqScore } = await import(chrome.runtime.getURL('src/lib/vidiq.js'));
|
||||
|
||||
const LINK_SELECTOR = 'a[href*="/watch?v="], a[href*="/shorts/"]';
|
||||
// Only used to find the element that contains a VidIQ badge; ID collection does not depend on it.
|
||||
const CARD_SELECTOR = [
|
||||
'ytd-rich-item-renderer',
|
||||
'ytd-video-renderer',
|
||||
'ytd-compact-video-renderer',
|
||||
'ytd-grid-video-renderer',
|
||||
'yt-lockup-view-model',
|
||||
'ytm-shorts-lockup-view-model',
|
||||
'ytd-reel-item-renderer',
|
||||
].join(',');
|
||||
const SCAN_INTERVAL_MS = 500;
|
||||
|
||||
// videoId -> true if already sent together with a VidIQ score, false if sent without one.
|
||||
let sent = new Map();
|
||||
let scanTimer = null;
|
||||
|
||||
function collectItems(page) {
|
||||
const items = new Map();
|
||||
const consider = (parsed, card) => {
|
||||
const state = sent.get(parsed.videoId);
|
||||
if (state === true) return;
|
||||
const vidiqScore = readVidiqScore(card);
|
||||
if (state === false && vidiqScore === null) return;
|
||||
const item = items.get(parsed.videoId) ?? {
|
||||
videoId: parsed.videoId,
|
||||
vidiqScore: null,
|
||||
viaShortsUrl: false,
|
||||
page,
|
||||
};
|
||||
item.vidiqScore ??= vidiqScore;
|
||||
item.viaShortsUrl ||= parsed.isShort;
|
||||
items.set(parsed.videoId, item);
|
||||
};
|
||||
|
||||
for (const link of document.querySelectorAll(LINK_SELECTOR)) {
|
||||
const parsed = parseVideoLink(link.getAttribute('href'));
|
||||
if (parsed) consider(parsed, link.closest(CARD_SELECTOR) ?? link.parentElement);
|
||||
}
|
||||
if (page === 'watch') {
|
||||
const current = parseVideoLink(location.href);
|
||||
if (current) consider(current, null);
|
||||
}
|
||||
return [...items.values()];
|
||||
}
|
||||
|
||||
function scan() {
|
||||
scanTimer = null;
|
||||
const page = pageFromPath(location.pathname);
|
||||
if (!page) return;
|
||||
const items = collectItems(page);
|
||||
if (items.length === 0) return;
|
||||
for (const item of items) sent.set(item.videoId, item.vidiqScore !== null);
|
||||
try {
|
||||
chrome.runtime.sendMessage({ type: 'videos-seen', items }).catch(() => {});
|
||||
} catch {
|
||||
observer.disconnect(); // the extension was reloaded; this script is orphaned
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleScan() {
|
||||
if (scanTimer === null) scanTimer = setTimeout(scan, SCAN_INTERVAL_MS);
|
||||
}
|
||||
|
||||
const observer = new MutationObserver(scheduleScan);
|
||||
observer.observe(document.documentElement, { childList: true, subtree: true });
|
||||
document.addEventListener('yt-navigate-finish', () => {
|
||||
sent = new Map();
|
||||
scheduleScan();
|
||||
});
|
||||
scheduleScan();
|
||||
})();
|
||||
Reference in New Issue
Block a user