use serde::{Deserialize, Serialize}; pub const DEFAULT_LOCAL_SINGBOX_LISTEN_HOST: &str = "127.0.0.1"; pub const DEFAULT_LOCAL_SINGBOX_LISTEN_PORT: u16 = 1080; pub const DEFAULT_LOCAL_SINGBOX_SERVICE_NAME: &str = "ProxyWardenSingBox"; pub const DEFAULT_LOCAL_SINGBOX_INSTALL_ROOT: &str = r"C:\Program Files\ProxyWarden\sing-box"; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum Protocol { Tcp, Udp, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum ProfileItemType { Process, Folder, Exe, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum TargetKind { Local, External, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum ProxyProtocol { Socks5, Http, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "kebab-case")] pub enum ComponentId { ControlApp, Proxyfier, Singbox, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum ComponentState { Installed, Missing, Stopped, Running, Error, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ProfileItemInput { #[serde(rename = "type")] pub item_type: String, pub value: String, #[serde(default)] pub recursive: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ProfileInput { pub id: Option, pub name: String, #[serde(default = "default_enabled")] pub enabled: bool, #[serde(default = "default_target_id")] pub target_id: String, #[serde(default = "default_protocols")] pub protocols: Vec, #[serde(default)] pub items: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ProfileItem { #[serde(rename = "type")] pub item_type: ProfileItemType, pub value: String, pub recursive: bool, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Profile { pub id: String, pub name: String, pub enabled: bool, pub target_id: String, pub protocols: Vec, pub items: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct TargetInput { pub id: Option, pub name: String, #[serde(default = "default_target_kind")] pub kind: String, #[serde(default = "default_proxy_protocol")] pub protocol: String, pub host: String, pub port: u32, #[serde(default)] pub requires_component: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Target { pub id: String, pub name: String, pub kind: TargetKind, pub protocol: ProxyProtocol, pub host: String, pub port: u16, pub requires_component: Option, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ComponentStatus { pub id: ComponentId, pub name: String, pub state: ComponentState, pub installed: bool, pub running: bool, pub version: Option, pub path: Option, #[serde(default)] pub problems: Vec, #[serde(default)] pub actions: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct LocalSingBoxConfig { #[serde(default)] pub subscription_url: Option, #[serde(default)] pub device_hwid: Option, #[serde(default)] pub selected_server_tag: Option, #[serde(default = "default_local_singbox_listen_host")] pub listen_host: String, #[serde(default = "default_local_singbox_listen_port")] pub listen_port: u16, #[serde(default = "default_local_singbox_service_name")] pub service_name: String, #[serde(default = "default_local_singbox_install_root")] pub install_root: String, #[serde(default)] pub updated_at: Option, } impl LocalSingBoxConfig { pub fn subscription_display_url(&self) -> Option { self.subscription_url .as_deref() .map(redact_subscription_url) } } impl Default for LocalSingBoxConfig { fn default() -> Self { Self { subscription_url: None, device_hwid: None, selected_server_tag: None, listen_host: default_local_singbox_listen_host(), listen_port: default_local_singbox_listen_port(), service_name: default_local_singbox_service_name(), install_root: default_local_singbox_install_root(), updated_at: None, } } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct SubscriptionCache { pub config: serde_json::Value, #[serde(default)] pub servers: Vec, #[serde(default)] pub user_info: serde_json::Map, pub fetched_at: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct SubscriptionServer { pub tag: String, #[serde(rename = "type")] pub server_type: String, pub server: String, pub server_port: u16, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ActivityEntry { pub id: String, pub at: String, pub level: ActivityLevel, pub title: String, pub message: String, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum ActivityLevel { Info, Warning, Error, Success, } fn default_enabled() -> bool { true } fn default_target_id() -> String { "local-singbox".to_string() } fn default_protocols() -> Vec { vec!["TCP".to_string(), "UDP".to_string()] } fn default_target_kind() -> String { "external".to_string() } fn default_proxy_protocol() -> String { "socks5".to_string() } fn default_local_singbox_listen_host() -> String { DEFAULT_LOCAL_SINGBOX_LISTEN_HOST.to_string() } fn default_local_singbox_listen_port() -> u16 { DEFAULT_LOCAL_SINGBOX_LISTEN_PORT } fn default_local_singbox_service_name() -> String { DEFAULT_LOCAL_SINGBOX_SERVICE_NAME.to_string() } fn default_local_singbox_install_root() -> String { DEFAULT_LOCAL_SINGBOX_INSTALL_ROOT.to_string() } pub fn redact_subscription_url(raw_url: &str) -> String { let trimmed = raw_url.trim(); if trimmed.is_empty() { return String::new(); } match trimmed.split_once("://") { Some((scheme, rest)) => { let host = rest .split(['/', '?', '#']) .next() .filter(|value| !value.is_empty()) .unwrap_or("subscription"); format!("{scheme}://{host}/...") } None => { let visible = trimmed.chars().take(18).collect::(); if trimmed.chars().count() <= 18 { "***".to_string() } else { format!("{visible}...") } } } }