use tauri::{AppHandle, Manager}; use reqwest::Client; use std::time::Duration; use adblock::{Engine, FilterSet}; use adblock::lists::ParseOptions; use super::engine::AdBlockState; const FILTER_URLS: &[(&str, &str)] = &[ ("easylist", "https://easylist.to/easylist/easylist.txt"), ("easyprivacy", "https://easylist.to/easylist/easyprivacy.txt"), ("fanboy-annoyances", "https://secure.fanboy.co.nz/fanboy-annoyance.txt"), ("ublock-filters", "https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/filters.txt"), ("ublock-privacy", "https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/privacy.txt"), ("ublock-unbreak", "https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/unbreak.txt"), ("ublock-quick-fixes", "https://raw.githubusercontent.com/uBlockOrigin/uAssets/master/filters/quick-fixes.txt"), ]; const SCRIPTLETS_URL: &str = "https://raw.githubusercontent.com/gorhill/uBlock/master/assets/resources/scriptlets.js"; const FALLBACK_SCRIPTLETS: &str = include_str!("../../resources/scriptlets/muse_ubo_compatible_scriptlets.js"); const UPDATE_INTERVAL: Duration = Duration::from_secs(24 * 60 * 60); pub fn spawn_updater(app: AppHandle) { tauri::async_runtime::spawn(async move { tokio::time::sleep(Duration::from_secs(30)).await; loop { if let Err(e) = update_now(&app).await { eprintln!("[muse-shield] Filter list update failed: {e}"); } tokio::time::sleep(UPDATE_INTERVAL).await; } }); } fn parse_scriptlet_file(path: &std::path::Path) -> Vec { #[allow(deprecated)] adblock::resources::resource_assembler::assemble_scriptlet_resources(path) } pub async fn update_now(app: &AppHandle) -> Result<(), String> { let client = Client::builder() .timeout(Duration::from_secs(45)) .user_agent("Muse/0.2 (adblock-updater)") .build() .map_err(|e| e.to_string())?; let mut filter_set = FilterSet::new(false); let mut total_rules = 0usize; for (name, url) in FILTER_URLS { match client.get(*url).send().await { Ok(resp) => { if let Ok(text) = resp.text().await { let _meta = filter_set.add_filter_list(&text, ParseOptions::default()); let rule_count = text.lines().filter(|l| !l.trim().is_empty() && !l.starts_with('!')).count(); total_rules += rule_count; println!("[muse-shield] Updated {name}: {rule_count} rules"); } } Err(e) => eprintln!("[muse-shield] Failed to fetch {name}: {e}"), } } if total_rules == 0 { return Ok(()); } let mut new_engine = Engine::from_filter_set(filter_set, true); let cache_dir = app.path().app_data_dir().map_err(|e| e.to_string())?; std::fs::create_dir_all(&cache_dir).map_err(|e| e.to_string())?; // 1) Try upstream uBO resource source. let mut resources = Vec::new(); match client.get(SCRIPTLETS_URL).send().await { Ok(resp) => { if let Ok(scriptlets_js) = resp.text().await { let scriptlets_path = cache_dir.join("ubo-scriptlets.js"); std::fs::write(&scriptlets_path, &scriptlets_js).map_err(|e| e.to_string())?; resources = parse_scriptlet_file(&scriptlets_path); println!("[muse-shield] Upstream uBO scriptlets parsed {} resources", resources.len()); } } Err(e) => eprintln!("[muse-shield] Failed to fetch upstream uBO scriptlets: {e}"), } // 2) Deterministic fallback bundle in adblock-rust legacy resource format. if resources.is_empty() { let fallback_path = cache_dir.join("muse-fallback-scriptlets.js"); std::fs::write(&fallback_path, FALLBACK_SCRIPTLETS).map_err(|e| e.to_string())?; resources = parse_scriptlet_file(&fallback_path); println!("[muse-shield] Fallback scriptlets parsed {} resources", resources.len()); } if !resources.is_empty() { let resource_count = resources.len(); new_engine.use_resources(resources); println!("[muse-shield] Loaded {resource_count} scriptlet resources via adblock-rust resource_assembler"); } else { eprintln!("[muse-shield] WARNING: scriptlet resource parsing returned 0 even for fallback bundle. Built-in early video scriptlets remain active."); } let state = app.state::(); state.reload_engine(new_engine, total_rules); println!("[muse-shield] Engine reloaded with {total_rules} rules + scriptlet resources"); Ok(()) }