Skip to content

Commit cc672e8

Browse files
committed
fix: merge fragmented content divs before Readability parses
archive.today snapshots (and some sites) lay out an article's body as several sibling divs interleaved with empty/display:none ones (stripped ad slots or embeds). Each div is scored independently by Readability's _grabArticle, and the interleaving breaks its sibling-merge heuristic enough that only the highest-scoring chunk survives, silently dropping the rest of the article. Collapsing runs of content+empty siblings into one node before Readability ever sees them avoids the fragmentation.
1 parent 9e85b2e commit cc672e8

1 file changed

Lines changed: 32 additions & 0 deletions

File tree

userscript.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,37 @@
378378
return art;
379379
}
380380

381+
// Some pages (notably archive.today snapshots) lay out an article's body
382+
// as several sibling <div>s interleaved with empty/display:none ones
383+
// (originally ad slots or embeds, stripped down but left in place). Each
384+
// real content div scores independently in Readability's _grabArticle(),
385+
// and the interleaving breaks its sibling-merge heuristic enough that
386+
// only the highest-scoring chunk gets kept — the rest of the article is
387+
// silently dropped. Collapsing each such run of siblings into one node
388+
// before Readability ever sees them avoids the fragmentation entirely.
389+
// Applied recursively (top-down, re-descending into the merged node)
390+
// since the same pattern can repeat at nested depths.
391+
function mergeFragmentedContent(root) {
392+
function process(el) {
393+
const children = Array.from(el.children);
394+
if (children.length >= 3) {
395+
const lens = children.map(c => c.textContent.trim().length);
396+
const substantial = children.filter((c, i) => lens[i] > 200);
397+
const trivial = children.filter((c, i) => lens[i] <= 50);
398+
if (substantial.length >= 2 && substantial.length + trivial.length === children.length) {
399+
const target = substantial[0];
400+
for (const c of children) {
401+
if (c === target) continue;
402+
while (c.firstChild) target.appendChild(c.firstChild);
403+
c.remove();
404+
}
405+
}
406+
}
407+
Array.from(el.children).forEach(process);
408+
}
409+
process(root);
410+
}
411+
381412
async function extract() {
382413
await ensureLibraries();
383414
await autoScrollFullPage();
@@ -389,6 +420,7 @@
389420
const lead = findLeadImage(document.body);
390421

391422
const docClone = document.cloneNode(true);
423+
mergeFragmentedContent(docClone.body);
392424
const reader = new unsafeWindow.Readability(docClone, { charThreshold: 100 });
393425
const art = reader.parse();
394426
return ensureLeadImage(art, lead);

0 commit comments

Comments
 (0)