mod chain; pub mod field; mod merge; mod script; pub mod seq; mod tun; use self::{ chain::{AsyncChainItemFrom as _, ChainItem, ChainType}, field::{use_keys, use_lowercase, use_sort}, merge::use_merge, script::use_script, seq::{SeqMap, use_seq}, tun::use_tun, }; use crate::constants; use crate::utils::dirs; use crate::{config::Config, utils::tmpl}; use crate::{logging, utils::logging::Type}; use serde_yaml_ng::Mapping; use smartstring::alias::String; use std::collections::{HashMap, HashSet}; use tokio::fs; type ResultLog = Vec<(String, String)>; #[derive(Debug)] struct ConfigValues { clash_config: Mapping, clash_core: Option, enable_tun: bool, enable_builtin: bool, socks_enabled: bool, http_enabled: bool, enable_dns_settings: bool, #[cfg(not(target_os = "windows"))] redir_enabled: bool, #[cfg(target_os = "linux")] tproxy_enabled: bool, } #[derive(Debug)] struct ProfileItems { config: Mapping, merge_item: ChainItem, script_item: ChainItem, rules_item: ChainItem, proxies_item: ChainItem, groups_item: ChainItem, global_merge: ChainItem, global_script: ChainItem, profile_name: String, } impl Default for ProfileItems { fn default() -> Self { Self { config: Default::default(), profile_name: Default::default(), merge_item: ChainItem { uid: "".into(), data: ChainType::Merge(Mapping::new()), }, script_item: ChainItem { uid: "".into(), data: ChainType::Script(tmpl::ITEM_SCRIPT.into()), }, rules_item: ChainItem { uid: "".into(), data: ChainType::Rules(SeqMap::default()), }, proxies_item: ChainItem { uid: "".into(), data: ChainType::Proxies(SeqMap::default()), }, groups_item: ChainItem { uid: "".into(), data: ChainType::Groups(SeqMap::default()), }, global_merge: ChainItem { uid: "Merge".into(), data: ChainType::Merge(Mapping::new()), }, global_script: ChainItem { uid: "Script".into(), data: ChainType::Script(tmpl::ITEM_SCRIPT.into()), }, } } } async fn get_config_values() -> ConfigValues { let clash_config = { Config::clash().await.latest_arc().0.clone() }; let (clash_core, enable_tun, enable_builtin, socks_enabled, http_enabled, enable_dns_settings) = { let verge = Config::verge().await; let verge = verge.latest_arc(); ( Some(verge.get_valid_clash_core()), verge.enable_tun_mode.unwrap_or(false), verge.enable_builtin_enhanced.unwrap_or(true), verge.verge_socks_enabled.unwrap_or(false), verge.verge_http_enabled.unwrap_or(false), verge.enable_dns_settings.unwrap_or(false), ) }; #[cfg(not(target_os = "windows"))] let redir_enabled = { let verge = Config::verge().await; let verge = verge.latest_arc(); verge.verge_redir_enabled.unwrap_or(false) }; #[cfg(target_os = "linux")] let tproxy_enabled = { let verge = Config::verge().await; let verge = verge.latest_arc(); verge.verge_tproxy_enabled.unwrap_or(false) }; ConfigValues { clash_config, clash_core, enable_tun, enable_builtin, socks_enabled, http_enabled, enable_dns_settings, #[cfg(not(target_os = "windows"))] redir_enabled, #[cfg(target_os = "linux")] tproxy_enabled, } } #[allow(clippy::cognitive_complexity)] async fn collect_profile_items() -> ProfileItems { // 从profiles里拿东西 - 先收集需要的数据,然后释放锁 let (current, merge_uid, script_uid, rules_uid, proxies_uid, groups_uid, name) = { let current = { let profiles = Config::profiles().await; let profiles_clone = profiles.latest_arc(); profiles_clone.current_mapping().await.unwrap_or_default() }; let profiles = Config::profiles().await; let profiles_ref = profiles.latest_arc(); let current_profile_uid = match profiles_ref.get_current() { Some(uid) => uid.clone(), None => return ProfileItems::default(), }; let current_item = match profiles_ref.get_item_arc(¤t_profile_uid) { Some(item) => item, None => return ProfileItems::default(), }; let merge_uid = current_item .current_merge() .unwrap_or_else(|| "Merge".into()); let script_uid = current_item .current_script() .unwrap_or_else(|| "Script".into()); let rules_uid = current_item .current_rules() .unwrap_or_else(|| "Rules".into()); let proxies_uid = current_item .current_proxies() .unwrap_or_else(|| "Proxies".into()); let groups_uid = current_item .current_groups() .unwrap_or_else(|| "Groups".into()); let name = profiles_ref .get_item(¤t_profile_uid) .ok() .and_then(|item| item.name.clone()) .unwrap_or_default(); ( current, merge_uid, script_uid, rules_uid, proxies_uid, groups_uid, name, ) }; // 现在获取具体的items,此时profiles锁已经释放 let merge_item = { let item = { let profiles = Config::profiles().await; let profiles = profiles.latest_arc(); profiles.get_item(&merge_uid).ok().cloned() }; if let Some(item) = item { >::from_async(&item).await } else { None } } .unwrap_or_else(|| ChainItem { uid: "".into(), data: ChainType::Merge(Mapping::new()), }); let script_item = { let item = { let profiles = Config::profiles().await; let profiles = profiles.latest_arc(); profiles.get_item(&script_uid).ok().cloned() }; if let Some(item) = item { >::from_async(&item).await } else { None } } .unwrap_or_else(|| ChainItem { uid: "".into(), data: ChainType::Script(tmpl::ITEM_SCRIPT.into()), }); let rules_item = { let item = { let profiles = Config::profiles().await; let profiles = profiles.latest_arc(); profiles.get_item(&rules_uid).ok().cloned() }; if let Some(item) = item { >::from_async(&item).await } else { None } } .unwrap_or_else(|| ChainItem { uid: "".into(), data: ChainType::Rules(SeqMap::default()), }); let proxies_item = { let item = { let profiles = Config::profiles().await; let profiles = profiles.latest_arc(); profiles.get_item(&proxies_uid).ok().cloned() }; if let Some(item) = item { >::from_async(&item).await } else { None } } .unwrap_or_else(|| ChainItem { uid: "".into(), data: ChainType::Proxies(SeqMap::default()), }); let groups_item = { let item = { let profiles = Config::profiles().await; let profiles = profiles.latest_arc(); profiles.get_item(&groups_uid).ok().cloned() }; if let Some(item) = item { >::from_async(&item).await } else { None } } .unwrap_or_else(|| ChainItem { uid: "".into(), data: ChainType::Groups(SeqMap::default()), }); let global_merge = { let item = { let profiles = Config::profiles().await; let profiles = profiles.latest_arc(); profiles.get_item("Merge").ok().cloned() }; if let Some(item) = item { >::from_async(&item).await } else { None } } .unwrap_or_else(|| ChainItem { uid: "Merge".into(), data: ChainType::Merge(Mapping::new()), }); let global_script = { let item = { let profiles = Config::profiles().await; let profiles = profiles.latest_arc(); profiles.get_item("Script").ok().cloned() }; if let Some(item) = item { >::from_async(&item).await } else { None } } .unwrap_or_else(|| ChainItem { uid: "Script".into(), data: ChainType::Script(tmpl::ITEM_SCRIPT.into()), }); ProfileItems { config: current, merge_item, script_item, rules_item, proxies_item, groups_item, global_merge, global_script, profile_name: name, } } fn process_global_items( mut config: Mapping, global_merge: ChainItem, global_script: ChainItem, profile_name: String, ) -> (Mapping, Vec, HashMap) { let mut result_map = HashMap::new(); let mut exists_keys = use_keys(&config); if let ChainType::Merge(merge) = global_merge.data { exists_keys.extend(use_keys(&merge)); config = use_merge(merge, config.to_owned()); } if let ChainType::Script(script) = global_script.data { let mut logs = vec![]; match use_script(script, config.to_owned(), profile_name) { Ok((res_config, res_logs)) => { exists_keys.extend(use_keys(&res_config)); config = res_config; logs.extend(res_logs); } Err(err) => logs.push(("exception".into(), err.to_string().into())), } result_map.insert(global_script.uid, logs); } (config, exists_keys, result_map) } #[allow(clippy::too_many_arguments)] fn process_profile_items( mut config: Mapping, mut exists_keys: Vec, mut result_map: HashMap, rules_item: ChainItem, proxies_item: ChainItem, groups_item: ChainItem, merge_item: ChainItem, script_item: ChainItem, profile_name: String, ) -> (Mapping, Vec, HashMap) { if let ChainType::Rules(rules) = rules_item.data { config = use_seq(rules, config.to_owned(), "rules"); } if let ChainType::Proxies(proxies) = proxies_item.data { config = use_seq(proxies, config.to_owned(), "proxies"); } if let ChainType::Groups(groups) = groups_item.data { config = use_seq(groups, config.to_owned(), "proxy-groups"); } if let ChainType::Merge(merge) = merge_item.data { exists_keys.extend(use_keys(&merge)); config = use_merge(merge, config.to_owned()); } if let ChainType::Script(script) = script_item.data { let mut logs = vec![]; match use_script(script, config.to_owned(), profile_name) { Ok((res_config, res_logs)) => { exists_keys.extend(use_keys(&res_config)); config = res_config; logs.extend(res_logs); } Err(err) => logs.push(("exception".into(), err.to_string().into())), } result_map.insert(script_item.uid, logs); } (config, exists_keys, result_map) } async fn merge_default_config( mut config: Mapping, clash_config: Mapping, socks_enabled: bool, http_enabled: bool, #[cfg(not(target_os = "windows"))] redir_enabled: bool, #[cfg(target_os = "linux")] tproxy_enabled: bool, ) -> Mapping { for (key, value) in clash_config.into_iter() { if key.as_str() == Some("tun") { let mut tun = config.get_mut("tun").map_or_else(Mapping::new, |val| { val.as_mapping().cloned().unwrap_or_else(Mapping::new) }); let patch_tun = value.as_mapping().cloned().unwrap_or_else(Mapping::new); for (key, value) in patch_tun.into_iter() { tun.insert(key, value); } config.insert("tun".into(), tun.into()); } else { if key.as_str() == Some("socks-port") && !socks_enabled { config.remove("socks-port"); continue; } if key.as_str() == Some("port") && !http_enabled { config.remove("port"); continue; } #[cfg(target_os = "windows")] { if key.as_str() == Some("redir-port") { continue; } } #[cfg(not(target_os = "windows"))] { if key.as_str() == Some("redir-port") && !redir_enabled { config.remove("redir-port"); continue; } } #[cfg(target_os = "linux")] { if key.as_str() == Some("tproxy-port") && !tproxy_enabled { config.remove("tproxy-port"); continue; } } #[cfg(not(target_os = "linux"))] { if key.as_str() == Some("tproxy-port") { config.remove("tproxy-port"); continue; } } // 处理 external-controller 键的开关逻辑 if key.as_str() == Some("external-controller") { let enable_external_controller = Config::verge() .await .latest_arc() .enable_external_controller .unwrap_or(false); if enable_external_controller { config.insert(key, value); } else { // 如果禁用了外部控制器,设置为空字符串 config.insert(key, "".into()); } } else { config.insert(key, value); } } } config } fn apply_builtin_scripts( mut config: Mapping, clash_core: Option, enable_builtin: bool, ) -> Mapping { if enable_builtin { ChainItem::builtin() .into_iter() .filter(|(s, _)| s.is_support(clash_core.as_ref())) .map(|(_, c)| c) .for_each(|item| { logging!(debug, Type::Core, "run builtin script {}", item.uid); if let ChainType::Script(script) = item.data { match use_script(script, config.to_owned(), "".into()) { Ok((res_config, _)) => { config = res_config; } Err(err) => { logging!(error, Type::Core, "builtin script error `{err}`"); } } } }); } config } async fn apply_dns_settings(mut config: Mapping, enable_dns_settings: bool) -> Mapping { if enable_dns_settings && let Ok(app_dir) = dirs::app_home_dir() { let dns_path = app_dir.join(constants::files::DNS_CONFIG); if dns_path.exists() && let Ok(dns_yaml) = fs::read_to_string(&dns_path).await && let Ok(dns_config) = serde_yaml_ng::from_str::(&dns_yaml) { if let Some(hosts_value) = dns_config.get("hosts") && hosts_value.is_mapping() { config.insert("hosts".into(), hosts_value.clone()); logging!(info, Type::Core, "apply hosts configuration"); } if let Some(dns_value) = dns_config.get("dns") { if let Some(dns_mapping) = dns_value.as_mapping() { config.insert("dns".into(), dns_mapping.clone().into()); logging!(info, Type::Core, "apply dns_config.yaml (dns section)"); } } else { config.insert("dns".into(), dns_config.into()); logging!(info, Type::Core, "apply dns_config.yaml"); } } } config } /// Enhance mode /// 返回最终订阅、该订阅包含的键、和script执行的结果 pub async fn enhance() -> (Mapping, Vec, HashMap) { // gather config values let cfg_vals = get_config_values().await; let ConfigValues { clash_config, clash_core, enable_tun, enable_builtin, socks_enabled, http_enabled, enable_dns_settings, #[cfg(not(target_os = "windows"))] redir_enabled, #[cfg(target_os = "linux")] tproxy_enabled, } = cfg_vals; // collect profile items let profile = collect_profile_items().await; let config = profile.config; let merge_item = profile.merge_item; let script_item = profile.script_item; let rules_item = profile.rules_item; let proxies_item = profile.proxies_item; let groups_item = profile.groups_item; let global_merge = profile.global_merge; let global_script = profile.global_script; let profile_name = profile.profile_name; // process globals let (config, exists_keys, result_map) = process_global_items(config, global_merge, global_script, profile_name.clone()); // process profile-specific items let (config, exists_keys, result_map) = process_profile_items( config, exists_keys, result_map, rules_item, proxies_item, groups_item, merge_item, script_item, profile_name, ); // merge default clash config let config = merge_default_config( config, clash_config, socks_enabled, http_enabled, #[cfg(not(target_os = "windows"))] redir_enabled, #[cfg(target_os = "linux")] tproxy_enabled, ) .await; // builtin scripts let mut config = apply_builtin_scripts(config, clash_core, enable_builtin); config = use_tun(config, enable_tun); config = use_sort(config); // dns settings config = apply_dns_settings(config, enable_dns_settings).await; let mut exists_set = HashSet::new(); exists_set.extend(exists_keys); let exists_keys: Vec = exists_set.into_iter().collect(); (config, exists_keys, result_map) }