use crate::capability::Capabilities; use crate::error::BexError; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct Manifest { pub schema: u32, pub id: String, pub name: String, pub version: String, pub authors: Vec, pub abi: String, pub provides: ProvidesSpec, pub network: NetworkSpec, pub storage: bool, #[serde(default)] pub secrets: Vec, /// Whether the plugin is allowed to use the JS evaluation engine. #[serde(default)] pub allow_js: bool, /// Whether the plugin's JS code can make HTTP requests via fetch polyfill. #[serde(default)] pub allow_js_fetch: bool, pub display: DisplaySpec, } #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] pub struct ProvidesSpec { #[serde(default)] pub home: bool, #[serde(default)] pub category: bool, #[serde(default)] pub search: bool, #[serde(default)] pub info: bool, #[serde(default)] pub servers: bool, #[serde(default)] pub stream: bool, #[serde(default)] pub subtitles: bool, #[serde(default)] pub articles: bool, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct NetworkSpec { #[serde(default = "default_hosts")] pub hosts: Vec, #[serde(default = "default_concurrent")] pub concurrent: usize, } #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] pub struct DisplaySpec { pub icon: Option, pub description: Option, #[serde(default)] pub tags: Vec, #[serde(default = "default_priority")] pub priority: u32, #[serde(default)] pub adult: bool, } fn default_hosts() -> Vec { vec!["*".into()] } fn default_concurrent() -> usize { 8 } fn default_priority() -> u32 { 100 } impl Manifest { pub fn validate(&self, host_version: &str) -> Result<(), BexError> { if self.schema != 1 { return Err(BexError::ManifestInvalid(format!("unsupported schema: {}", self.schema))); } if !self.id.contains('.') || self.id.len() < 5 { return Err(BexError::ManifestInvalid(format!("id '{}' must be reverse-DNS", self.id))); } let req = semver::VersionReq::parse(&self.abi) .map_err(|e| BexError::ManifestInvalid(format!("abi: {e}")))?; let host = semver::Version::parse(host_version) .map_err(|e| BexError::ManifestInvalid(format!("host: {e}")))?; if !req.matches(&host) { return Err(BexError::AbiMismatch { host: host_version.into(), plugin_requires: self.abi.clone() }); } Ok(()) } pub fn allows_host(&self, host: &str) -> bool { self.network.hosts.iter().any(|p| { if p == "*" { return true; } if let Some(suffix) = p.strip_prefix("*.") { host.ends_with(&format!(".{suffix}")) || host == suffix } else { host == p } }) } pub fn capabilities(&self) -> Capabilities { Capabilities::from_manifest(&self.provides) } }