diff --git a/popup/popup.css b/popup/popup.css index fa8524e..8c28f44 100644 --- a/popup/popup.css +++ b/popup/popup.css @@ -589,7 +589,7 @@ body { max-width: 280px; } -/* ── YouTube Error ── */ +/* ── Unsupported extraction (YouTube, streaming, Bluesky, email UIs, etc.) ── */ .youtube-error { display: flex; flex-direction: column; diff --git a/popup/popup.js b/popup/popup.js index 3be6fd8..d30332e 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -443,9 +443,12 @@ document.addEventListener("DOMContentLoaded", async () => { return; } - // Check if this is a YouTube video - if (isYoutubeUrl(currentTabUrl)) { - showYoutubeError(); + const unsupportedExtraction = getUnsupportedExtractionInfo(currentTabUrl); + if (unsupportedExtraction) { + showUnsupportedExtractionError( + unsupportedExtraction.title, + unsupportedExtraction.message, + ); return; } @@ -1531,18 +1534,77 @@ function isPdfUrl(url) { return pathname.endsWith(".pdf") || pathname.includes(".pdf?"); } -function isYoutubeUrl(url) { - if (!url) return false; - // Check for YouTube video URLs (youtube.com/watch or youtu.be/short) - const urlObj = new URL(url); - const hostname = urlObj.hostname.toLowerCase(); - const pathname = urlObj.pathname.toLowerCase(); - // Match youtube.com/watch?v= or youtu.be/VIDEO_ID - return ( - ((hostname === "www.youtube.com" || hostname === "youtube.com") && - pathname === "/watch") || - hostname === "youtu.be" - ); +function unsupportedHostSuffixMatches(hostname, suffix) { + const s = String(suffix).toLowerCase(); + const h = hostname.toLowerCase(); + return h === s || h.endsWith("." + s); +} + +function unsupportedUrlRuleMatches(hostname, pathname, anyOf) { + if (!anyOf || !anyOf.length) return false; + for (const part of anyOf) { + if (part.hostEquals) { + if (hostname === String(part.hostEquals).toLowerCase()) { + return true; + } + continue; + } + if (part.hostIn) { + const hosts = part.hostIn.map((x) => String(x).toLowerCase()); + if (!hosts.includes(hostname)) continue; + if (part.pathnameEquals !== undefined) { + if (pathname === String(part.pathnameEquals).toLowerCase()) { + return true; + } + continue; + } + return true; + } + } + return false; +} + +/** + * Sites where page text isn't extractable like a normal article (streaming, apps, banking, etc.). + * Rules live in `CONFIG.UNSUPPORTED_EXTRACTION.RULES` (`scripts/config.js`). + */ +function getUnsupportedExtractionInfo(url) { + const rules = + typeof CONFIG !== "undefined" && CONFIG.UNSUPPORTED_EXTRACTION?.RULES; + if (!url || !rules?.length) return null; + + let hostname; + let pathname = ""; + try { + const u = new URL(url); + hostname = u.hostname.toLowerCase(); + pathname = u.pathname.toLowerCase(); + } catch { + return null; + } + + for (const rule of rules) { + if (rule.kind === "url") { + if (unsupportedUrlRuleMatches(hostname, pathname, rule.anyOf)) { + return { title: rule.title, message: rule.message }; + } + } else if (rule.kind === "host") { + if (unsupportedHostSuffixMatches(hostname, rule.hostSuffix)) { + return { title: rule.title, message: rule.message }; + } + } else if (rule.kind === "labeled") { + for (const [suffix, label] of rule.sites) { + if (unsupportedHostSuffixMatches(hostname, suffix)) { + return { + title: `${label} isn't supported`, + message: rule.message, + }; + } + } + } + } + + return null; } function showPdfError() { @@ -1564,8 +1626,7 @@ function showPdfError() { `; } -function showYoutubeError() { - // Hide initial state and show error +function showUnsupportedExtractionError(title, message) { initialState.classList.add("hidden"); resultContainer.classList.remove("hidden"); footer.classList.add("hidden"); @@ -1574,10 +1635,8 @@ function showYoutubeError() { resultContainer.innerHTML = `