use crate::dom::{LuaNode, parse_html}; use crate::rss::FeedItem; use mlua::{Lua, Table}; #[derive(Debug)] pub enum RunError { Lua(mlua::Error), MissingField(&'static str), } impl std::fmt::Display for RunError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { RunError::Lua(e) => write!(f, "method script failed: {e}"), RunError::MissingField(field) => write!( f, "an item returned by the method script is missing required field `{field}`" ), } } } impl std::error::Error for RunError {} /// Runs a method script against fetched HTML. A fresh `Lua` VM and DOM tree /// are created per call: `mlua::Lua` and `rcdom::Handle` are not `Send`/`Sync`, /// and the scheduler dispatches one fetch per worker thread, so nothing here /// is meant to outlive the thread that calls this function. /// /// The script is called with `(root, url)`: the parsed DOM and the site's /// own fetch URL, the latter needed to resolve relative links a page emits /// (`href="/foo"`) into absolute ones. A script that doesn't need it can /// still write `local root = ...` — Lua drops the extra value silently. A /// `fetch(url) -> {status=, body=}` global is also available, for a script /// that needs a second page (e.g. an article's own page for its full body), /// along with `parse_html(html) -> node` to walk whatever it fetches. pub fn run_method(source: &str, url: &str, html: &str) -> Result, RunError> { let lua = Lua::new(); crate::http_fetch::register(&lua).map_err(RunError::Lua)?; crate::dom::register(&lua).map_err(RunError::Lua)?; let dom = parse_html(html); let root = LuaNode::root(&dom); let items: Table = lua.load(source).call((root, url)).map_err(RunError::Lua)?; let mut out = Vec::new(); for entry in items.sequence_values::() { let item = entry.map_err(RunError::Lua)?; let title = item .get::>("title") .map_err(RunError::Lua)? .ok_or(RunError::MissingField("title"))?; let link = item .get::>("link") .map_err(RunError::Lua)? .ok_or(RunError::MissingField("link"))?; let description = item .get::>("description") .map_err(RunError::Lua)?; let pub_date = item .get::>("pub_date") .map_err(RunError::Lua)?; let guid = item.get::>("guid").map_err(RunError::Lua)?; let author = item .get::>("author") .map_err(RunError::Lua)?; out.push(FeedItem { title, link, description, pub_date, guid, author, }); } Ok(out) }