use crate::utils::{config, dirs}; use anyhow::Result; use serde::{Deserialize, Serialize}; use serde_yaml::{Mapping, Value}; #[derive(Default, Debug, Clone, Deserialize, Serialize)] pub struct ClashInfo { /// clash sidecar status pub status: String, /// clash core port pub port: Option, /// same as `external-controller` pub server: Option, /// clash secret pub secret: Option, } impl ClashInfo { /// parse the clash's config.yaml /// get some information pub fn from(config: &Mapping) -> ClashInfo { let key_port_1 = Value::from("mixed-port"); let key_port_2 = Value::from("port"); let key_server = Value::from("external-controller"); let key_secret = Value::from("secret"); let mut status: u32 = 0; let port = match config.get(&key_port_1) { Some(value) => match value { Value::String(val_str) => Some(val_str.clone()), Value::Number(val_num) => Some(val_num.to_string()), _ => { status |= 0b1; None } }, _ => { status |= 0b10; None } }; let port = match port { Some(_) => port, None => match config.get(&key_port_2) { Some(value) => match value { Value::String(val_str) => Some(val_str.clone()), Value::Number(val_num) => Some(val_num.to_string()), _ => { status |= 0b100; None } }, _ => { status |= 0b1000; None } }, }; // `external-controller` could be // "127.0.0.1:9090" or ":9090" let server = match config.get(&key_server) { Some(value) => match value.as_str() { Some(val_str) => { if val_str.starts_with(":") { Some(format!("127.0.0.1{val_str}")) } else if val_str.starts_with("0.0.0.0:") { Some(format!("127.0.0.1:{}", &val_str[8..])) } else if val_str.starts_with("[::]:") { Some(format!("127.0.0.1:{}", &val_str[5..])) } else { Some(val_str.into()) } } None => { status |= 0b10000; None } }, None => { status |= 0b100000; None } }; let secret = match config.get(&key_secret) { Some(value) => match value { Value::String(val_str) => Some(val_str.clone()), Value::Bool(val_bool) => Some(val_bool.to_string()), Value::Number(val_num) => Some(val_num.to_string()), _ => None, }, _ => None, }; ClashInfo { status: format!("{status}"), port, server, secret, } } } #[derive(Debug)] pub struct Clash { /// maintain the clash config pub config: Mapping, /// some info pub info: ClashInfo, } impl Clash { pub fn new() -> Clash { let config = Clash::read_config(); let info = ClashInfo::from(&config); Clash { config, info } } /// get clash config pub fn read_config() -> Mapping { config::read_merge_mapping(dirs::clash_path()) } /// save the clash config pub fn save_config(&self) -> Result<()> { config::save_yaml( dirs::clash_path(), &self.config, Some("# Default Config For Clash Core\n\n"), ) } /// patch update the clash config /// if the port is changed then return true pub fn patch_config(&mut self, patch: Mapping) -> Result<()> { let port_key = Value::from("mixed-port"); let server_key = Value::from("external-controller"); let secret_key = Value::from("secret"); let change_info = patch.contains_key(&port_key) || patch.contains_key(&server_key) || patch.contains_key(&secret_key); for (key, value) in patch.into_iter() { self.config.insert(key, value); } if change_info { self.info = ClashInfo::from(&self.config); } self.save_config() } } impl Default for Clash { fn default() -> Self { Clash::new() } }