1
Fork 0
mirror of https://github.com/thegeneralist01/archivr synced 2026-07-22 03:05:32 +02:00

fix: use correct single-file hook event (single-file-on-before-capture-request)

Prior scripts listened on 'single-file-on-before-capture-start' which
does not exist in single-file-core 1.1.49.  The real hook is:

  single-file-on-before-capture-request  (dispatched by initUserScriptHandler
  after receiving single-file-user-script-init; userScriptEnabled defaults
  to true in args.js so it always fires when --browser-script is passed)

Changes:
- strip-scripts: -start -> -request (no preventDefault needed; synchronous)
- READER_MODE_SCRIPT: -start -> -request; add 'installed' meta marker at
  script-evaluation time so artifact inspection can distinguish 'script
  not injected' / 'hook never fired' / 'Readability parse failed'
This commit is contained in:
TheGeneralist 2026-07-08 10:35:30 +02:00
parent ef8b5ac83b
commit b4e092f10d
Signed by: thegeneralist01
SSH key fingerprint: SHA256:pp9qddbCNmVNoSjevdvQvM5z0DHN7LTa8qBMbcMq/R4

View file

@ -13,18 +13,47 @@ use crate::downloader::cookies::{domain_from_url, write_netscape_cookie_file};
use crate::hash::hash_file;
/// Mozilla Readability.js (Apache 2.0) — embedded at compile time so captures
/// don't need it on PATH. Path is relative to this source file.
const READABILITY_JS: &str =
include_str!("../../../../vendor/readability/Readability.js");
/// Single-file user script that applies Readability just before the page is
/// serialised. Fires on `single-file-on-before-capture-start`.
const READER_MODE_WRAPPER_JS: &str = r#"
addEventListener('single-file-on-before-capture-start', function() {
/// Combined reader-mode script: Readability.js (Apache 2.0) bundled with the
/// archivr wrapper in a single IIFE so both share the same execution scope —
/// passing them as separate `--browser-script` files can put each in its own
/// context (observed with single-file-cli 1.1.49), making Readability invisible
/// to the wrapper.
///
/// Emits `<meta name="archivr-reader-mode" content="applied|failed:REASON">`
/// so the outcome is observable in the saved HTML.
const READER_MODE_SCRIPT: &str = concat!(
// Readability.js is injected verbatim first so `Readability` is in scope.
include_str!("../../../../vendor/readability/Readability.js"),
// Wrapper IIFE — runs on single-file-on-before-capture-request.
// Sets 'installed' immediately at script-evaluation time so a missing meta
// means the browser-script was never injected at all.
r#"
;(function() {
function _archivrReaderMark(content) {
try {
if (typeof Readability === 'undefined') return;
var m = document.querySelector('meta[name="archivr-reader-mode"]');
if (!m) {
m = document.createElement('meta');
m.name = 'archivr-reader-mode';
(document.head || document.documentElement).appendChild(m);
}
m.content = content;
} catch(_) {}
}
// Mark immediately: if this meta is absent in the artifact the script
// was never injected (separate from the hook never firing).
_archivrReaderMark('installed');
function _archivrApplyReader() {
try {
if (typeof Readability === 'undefined') {
_archivrReaderMark('failed:no-readability');
return;
}
var article = new Readability(document.cloneNode(true)).parse();
if (!article || !article.content || article.content.length < 100) return;
if (!article || !article.content || article.content.length < 100) {
_archivrReaderMark('failed:no-article');
return;
}
document.body.innerHTML = article.content;
if (article.title) document.title = article.title;
var hdr = document.createElement('header');
@ -53,9 +82,16 @@ addEventListener('single-file-on-before-capture-start', function() {
'blockquote{border-left:3px solid #ccc;margin:1em 0;padding-left:1.2em;color:#555}',
].join('');
document.head.appendChild(style);
} catch (e) { /* non-fatal: fall back to original page */ }
});
"#;
_archivrReaderMark('applied');
} catch (e) {
_archivrReaderMark('failed:exception:' + (e && e.message ? e.message : String(e)));
}
}
// Synchronous work — no preventDefault()/response dispatch needed.
addEventListener('single-file-on-before-capture-request', _archivrApplyReader);
})();
"#
);
/// Result of archiving a web page with single-file.
#[derive(Debug)]
@ -183,24 +219,22 @@ fn save_with(
let strip_scripts_path = temp_dir.join("sf-strip-scripts.js");
std::fs::write(
&strip_scripts_path,
"addEventListener('single-file-on-before-capture-start',()=>{\
"addEventListener('single-file-on-before-capture-request',()=>{\
document.querySelectorAll('script:not([type=\"application/ld+json\"])')\
.forEach(el=>el.remove());\
});",
)
.context("failed to write single-file user script")?;
// Optional reader-mode scripts (Readability.js + wrapper).
// Optional reader-mode script: Readability.js + wrapper combined into one
// file so both run in the same execution scope. (Separate --browser-script
// files can each get their own context depending on single-file version.)
let mut extra_browser_scripts: Vec<PathBuf> = Vec::new();
if reader_mode {
let readability_path = temp_dir.join("sf-readability.js");
std::fs::write(&readability_path, READABILITY_JS)
.context("failed to write Readability.js script")?;
let wrapper_path = temp_dir.join("sf-reader-mode.js");
std::fs::write(&wrapper_path, READER_MODE_WRAPPER_JS)
.context("failed to write reader-mode wrapper script")?;
extra_browser_scripts.push(readability_path);
extra_browser_scripts.push(wrapper_path);
let reader_path = temp_dir.join("sf-reader-mode.js");
std::fs::write(&reader_path, READER_MODE_SCRIPT)
.context("failed to write reader-mode script")?;
extra_browser_scripts.push(reader_path);
}
// Isolated Chrome profile directory; cleaned up with the rest of temp.