summaryrefslogtreecommitdiff
path: root/src/extract.ts
diff options
context:
space:
mode:
authorIgor Soarez <igor@soarez.org>2026-08-03 22:09:48 +0100
committerIgor Soarez <igor@soarez.org>2026-08-03 22:09:48 +0100
commit7a8f261ec50b9c15bc37ba8ae1f0022c392111d3 (patch)
tree2157aaa360c41577910267fb513a9980920cc743 /src/extract.ts
parent97b7804d02d0a8ffc9afdc64069069baf93ef392 (diff)
Take Brave snippets by position rather than by class name
Brave's source name was leaking into every snippet ("Medium March 27, 2025 - ..."), because the .sitename/.netloc selectors guessed for it match nothing. Rather than guess again, use the ordering: an engine rendering "source / breadcrumb / title / description" puts all its metadata before the title, so everything after the title line is the description. Class names churn; that ordering does not. The named selectors stay as the fallback for when the title is not on a line of its own. Verified against Brave, and Google/DuckDuckGo/Bing re-checked for regressions.
Diffstat (limited to 'src/extract.ts')
-rw-r--r--src/extract.ts45
1 files changed, 32 insertions, 13 deletions
diff --git a/src/extract.ts b/src/extract.ts
index 282fa07..8b5c57f 100644
--- a/src/extract.ts
+++ b/src/extract.ts
@@ -177,21 +177,40 @@ export const buildProbeScript = (config: ExtractionConfig): string => String.raw
if (snippetEl) {
snippet = norm(snippetEl.innerText);
} else {
- // No dedicated description element: subtract the parts we can name
- // (title, breadcrumb, source) and keep what is left.
- var drop = {};
- for (var s = 0; CFG.subtract && s < CFG.subtract.length; s++) {
- var parts = item.querySelectorAll(CFG.subtract[s]);
- for (var p = 0; p < parts.length; p++) {
- var pl = linesOf(parts[p]);
- for (var q = 0; q < pl.length; q++) drop[norm(pl[q])] = true;
+ // No dedicated description element. Rather than guess at class names
+ // for the source and breadcrumb, use their position: an engine that
+ // renders "source / breadcrumb / title / description" puts every piece
+ // of metadata *before* the title, so everything after the title line is
+ // the description. Class names churn; that ordering does not.
+ var itemLines = linesOf(item);
+ var titleIndex = -1;
+ for (var t = 0; t < itemLines.length; t++) {
+ if (norm(itemLines[t]) === title) { titleIndex = t; break; }
+ }
+
+ var candidate;
+ if (titleIndex >= 0) {
+ candidate = itemLines.slice(titleIndex + 1);
+ } else {
+ // Title is not its own line (it may be inline with other text).
+ // Fall back to subtracting the parts we can name.
+ var drop = {};
+ for (var s = 0; CFG.subtract && s < CFG.subtract.length; s++) {
+ var parts = item.querySelectorAll(CFG.subtract[s]);
+ for (var p = 0; p < parts.length; p++) {
+ var pl = linesOf(parts[p]);
+ for (var q = 0; q < pl.length; q++) drop[norm(pl[q])] = true;
+ }
}
+ candidate = itemLines.filter(function (l) { return !drop[norm(l)]; });
}
- var kept = linesOf(item).filter(function (l) {
- var n = norm(l);
- return !drop[n] && n !== title && !/^https?:\/\//.test(n) && n.indexOf("›") === -1;
- });
- snippet = norm(kept.join(" "));
+
+ snippet = norm(
+ candidate.filter(function (l) {
+ var n = norm(l);
+ return n !== title && !/^https?:\/\//.test(n) && n.indexOf("›") === -1;
+ }).join(" "),
+ );
}
seen[url] = true;