69 lines
2.5 KiB
JavaScript
69 lines
2.5 KiB
JavaScript
const MEDIA_EXT_RE = /\.(mp4|m4v|webm|mkv|mov|m3u8|mpd)([?#]|$)/i;
|
|
const ABSOLUTE_MEDIA_RE = /(?:https?:\/\/|https?:\\\/\\\/|\/\/|\\\/\\\/)[^\s"'<>]+?\.(?:mp4|m4v|webm|mkv|mov|m3u8|mpd)(?=$|[?&#\s"'<>])(?:[?&][^\s"'<>]*)?/gi;
|
|
|
|
function ignoreChromeResult(result) {
|
|
if (result && typeof result.catch === 'function') result.catch(() => {});
|
|
}
|
|
|
|
function normalizeMediaUrl(rawUrl, baseUrl = location.href) {
|
|
if (!rawUrl || typeof rawUrl !== 'string') return null;
|
|
try {
|
|
const cleaned = rawUrl
|
|
.trim()
|
|
.replaceAll('\\/', '/')
|
|
.replaceAll('\\u002F', '/')
|
|
.replaceAll('\\u002f', '/')
|
|
.replace(/[;,)}\]]+$/, '');
|
|
return new URL(cleaned, baseUrl).href;
|
|
} catch (_) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function pushCandidate(candidates, rawUrl, title = document.title, contentType = '', source = 'dom') {
|
|
const url = normalizeMediaUrl(rawUrl);
|
|
if (!url) return;
|
|
const cleanType = String(contentType || '').split(';', 1)[0].trim();
|
|
if (MEDIA_EXT_RE.test(url) || cleanType.startsWith('video/') || /mpegurl|dash\+xml/i.test(cleanType)) {
|
|
candidates.push({url, title: title || document.title, content_type: contentType || '', source});
|
|
}
|
|
}
|
|
|
|
function collectCandidates() {
|
|
const candidates = [];
|
|
document.querySelectorAll('video,source,a').forEach((el) => {
|
|
pushCandidate(candidates, el.currentSrc || el.src || el.href, el.title || el.textContent || document.title, el.type || '', 'dom');
|
|
});
|
|
|
|
performance.getEntriesByType('resource').forEach((entry) => {
|
|
pushCandidate(candidates, entry.name, document.title, entry.initiatorType === 'video' ? 'video/unknown' : '', `performance:${entry.initiatorType || 'resource'}`);
|
|
});
|
|
|
|
const html = document.documentElement?.outerHTML || '';
|
|
for (const match of html.matchAll(ABSOLUTE_MEDIA_RE)) {
|
|
pushCandidate(candidates, match[0], document.title, '', 'html-js');
|
|
}
|
|
|
|
const seen = new Set();
|
|
return candidates.filter((candidate) => {
|
|
if (seen.has(candidate.url)) return false;
|
|
seen.add(candidate.url);
|
|
return true;
|
|
}).slice(0, 120);
|
|
}
|
|
|
|
chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|
if (message?.type === 'collect-media') {
|
|
sendResponse({candidates: collectCandidates()});
|
|
return true;
|
|
}
|
|
return false;
|
|
});
|
|
|
|
// Opportunistisch nach dem Laden melden, damit das Popup sofort Daten hat.
|
|
setTimeout(() => {
|
|
for (const candidate of collectCandidates()) {
|
|
ignoreChromeResult(chrome.runtime.sendMessage({type: 'candidate', candidate}));
|
|
}
|
|
}, 1500);
|