diff --git a/src-tauri/src/config/clash.rs b/src-tauri/src/config/clash.rs index 7f782a3..e1d4aa3 100644 --- a/src-tauri/src/config/clash.rs +++ b/src-tauri/src/config/clash.rs @@ -13,7 +13,7 @@ pub struct IClashTemp(pub Mapping); impl IClashTemp { pub fn new() -> Self { let template = Self::template(); - match dirs::clash_path().and_then(|path| help::read_merge_mapping(&path)) { + match dirs::clash_path().and_then(|path| help::read_mapping(&path)) { Ok(mut map) => { template.0.keys().for_each(|key| { if !map.contains_key(key) { diff --git a/src-tauri/src/config/prfitem.rs b/src-tauri/src/config/prfitem.rs index 22a378f..32d846f 100644 --- a/src-tauri/src/config/prfitem.rs +++ b/src-tauri/src/config/prfitem.rs @@ -152,8 +152,6 @@ impl PrfItem { let desc = item.desc.unwrap_or("".into()); PrfItem::from_local(name, desc, file_data, item.option) } - "merge" => PrfItem::from_merge(), - "script" => PrfItem::from_script(), typ => bail!("invalid profile item type \"{typ}\""), } } @@ -166,15 +164,15 @@ impl PrfItem { file_data: Option, option: Option, ) -> Result { - let uid = help::get_uid("l"); + let uid = help::get_uid("L"); let file = format!("{uid}.yaml"); let opt_ref = option.as_ref(); let update_interval = opt_ref.and_then(|o| o.update_interval); let mut merge = opt_ref.and_then(|o| o.merge.clone()); let mut script = opt_ref.and_then(|o| o.script.clone()); - let rules = opt_ref.and_then(|o| o.rules.clone()); - let proxies = opt_ref.and_then(|o| o.proxies.clone()); - let groups = opt_ref.and_then(|o| o.groups.clone()); + let mut rules = opt_ref.and_then(|o| o.rules.clone()); + let mut proxies = opt_ref.and_then(|o| o.proxies.clone()); + let mut groups = opt_ref.and_then(|o| o.groups.clone()); if merge.is_none() { let merge_item = PrfItem::from_merge()?; @@ -186,6 +184,23 @@ impl PrfItem { Config::profiles().data().append_item(script_item.clone())?; script = script_item.uid; } + if rules.is_none() { + let rules_item = PrfItem::from_rules()?; + Config::profiles().data().append_item(rules_item.clone())?; + rules = rules_item.uid; + } + if proxies.is_none() { + let proxies_item = PrfItem::from_proxies()?; + Config::profiles() + .data() + .append_item(proxies_item.clone())?; + proxies = proxies_item.uid; + } + if groups.is_none() { + let groups_item = PrfItem::from_groups()?; + Config::profiles().data().append_item(groups_item.clone())?; + groups = groups_item.uid; + } Ok(PrfItem { uid: Some(uid), itype: Some("local".into()), @@ -227,9 +242,9 @@ impl PrfItem { let update_interval = opt_ref.and_then(|o| o.update_interval); let mut merge = opt_ref.and_then(|o| o.merge.clone()); let mut script = opt_ref.and_then(|o| o.script.clone()); - let rules = opt_ref.and_then(|o| o.rules.clone()); - let proxies = opt_ref.and_then(|o| o.proxies.clone()); - let groups = opt_ref.and_then(|o| o.groups.clone()); + let mut rules = opt_ref.and_then(|o| o.rules.clone()); + let mut proxies = opt_ref.and_then(|o| o.proxies.clone()); + let mut groups = opt_ref.and_then(|o| o.groups.clone()); let mut builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy(); if merge.is_none() { @@ -242,6 +257,23 @@ impl PrfItem { Config::profiles().data().append_item(script_item.clone())?; script = script_item.uid; } + if rules.is_none() { + let rules_item = PrfItem::from_rules()?; + Config::profiles().data().append_item(rules_item.clone())?; + rules = rules_item.uid; + } + if proxies.is_none() { + let proxies_item = PrfItem::from_proxies()?; + Config::profiles() + .data() + .append_item(proxies_item.clone())?; + proxies = proxies_item.uid; + } + if groups.is_none() { + let groups_item = PrfItem::from_groups()?; + Config::profiles().data().append_item(groups_item.clone())?; + groups = groups_item.uid; + } // 使用软件自己的代理 if self_proxy { let port = Config::verge() @@ -352,7 +384,7 @@ impl PrfItem { None => None, }; - let uid = help::get_uid("r"); + let uid = help::get_uid("R"); let file = format!("{uid}.yaml"); let name = name.unwrap_or(filename.unwrap_or("Remote File".into())); let data = resp.text_with_charset("utf-8").await?; @@ -436,6 +468,69 @@ impl PrfItem { }) } + /// ## Rules type (enhance) + pub fn from_rules() -> Result { + let uid = help::get_uid("r"); + let file = format!("{uid}.yaml"); // yaml ext + + Ok(PrfItem { + uid: Some(uid), + itype: Some("rules".into()), + name: None, + desc: None, + file: Some(file), + url: None, + home: None, + selected: None, + extra: None, + option: None, + updated: Some(chrono::Local::now().timestamp() as usize), + file_data: Some(tmpl::ITEM_RULES.into()), + }) + } + + /// ## Proxies type (enhance) + pub fn from_proxies() -> Result { + let uid = help::get_uid("p"); + let file = format!("{uid}.yaml"); // yaml ext + + Ok(PrfItem { + uid: Some(uid), + itype: Some("proxies".into()), + name: None, + desc: None, + file: Some(file), + url: None, + home: None, + selected: None, + extra: None, + option: None, + updated: Some(chrono::Local::now().timestamp() as usize), + file_data: Some(tmpl::ITEM_PROXIES.into()), + }) + } + + /// ## Groups type (enhance) + pub fn from_groups() -> Result { + let uid = help::get_uid("g"); + let file = format!("{uid}.yaml"); // yaml ext + + Ok(PrfItem { + uid: Some(uid), + itype: Some("groups".into()), + name: None, + desc: None, + file: Some(file), + url: None, + home: None, + selected: None, + extra: None, + option: None, + updated: Some(chrono::Local::now().timestamp() as usize), + file_data: Some(tmpl::ITEM_GROUPS.into()), + }) + } + /// get the file data pub fn read_file(&self) -> Result { if self.file.is_none() { diff --git a/src-tauri/src/config/profiles.rs b/src-tauri/src/config/profiles.rs index 2283c22..08ef1f9 100644 --- a/src-tauri/src/config/profiles.rs +++ b/src-tauri/src/config/profiles.rs @@ -246,9 +246,9 @@ impl IProfiles { let mut index = None; let mut merge_index = None; let mut script_index = None; - // let mut rules_index = None; - // let mut proxies_index = None; - // let mut groups_index = None; + let mut rules_index = None; + let mut proxies_index = None; + let mut groups_index = None; // get the index for (i, _) in items.iter().enumerate() { @@ -257,7 +257,6 @@ impl IProfiles { break; } } - if let Some(index) = index { if let Some(file) = items.remove(index).file { let _ = dirs::app_profiles_dir().map(|path| { @@ -268,7 +267,6 @@ impl IProfiles { }); } } - // get the merge index for (i, _) in items.iter().enumerate() { if items[i].uid == merge_uid { @@ -276,7 +274,6 @@ impl IProfiles { break; } } - if let Some(index) = merge_index { if let Some(file) = items.remove(index).file { let _ = dirs::app_profiles_dir().map(|path| { @@ -287,7 +284,6 @@ impl IProfiles { }); } } - // get the script index for (i, _) in items.iter().enumerate() { if items[i].uid == script_uid { @@ -295,7 +291,6 @@ impl IProfiles { break; } } - if let Some(index) = script_index { if let Some(file) = items.remove(index).file { let _ = dirs::app_profiles_dir().map(|path| { @@ -306,7 +301,57 @@ impl IProfiles { }); } } - + // get the rules index + for (i, _) in items.iter().enumerate() { + if items[i].uid == rules_uid { + rules_index = Some(i); + break; + } + } + if let Some(index) = rules_index { + if let Some(file) = items.remove(index).file { + let _ = dirs::app_profiles_dir().map(|path| { + let path = path.join(file); + if path.exists() { + let _ = fs::remove_file(path); + } + }); + } + } + // get the proxies index + for (i, _) in items.iter().enumerate() { + if items[i].uid == proxies_uid { + proxies_index = Some(i); + break; + } + } + if let Some(index) = proxies_index { + if let Some(file) = items.remove(index).file { + let _ = dirs::app_profiles_dir().map(|path| { + let path = path.join(file); + if path.exists() { + let _ = fs::remove_file(path); + } + }); + } + } + // get the groups index + for (i, _) in items.iter().enumerate() { + if items[i].uid == groups_uid { + groups_index = Some(i); + break; + } + } + if let Some(index) = groups_index { + if let Some(file) = items.remove(index).file { + let _ = dirs::app_profiles_dir().map(|path| { + let path = path.join(file); + if path.exists() { + let _ = fs::remove_file(path); + } + }); + } + } // delete the original uid if current == uid { self.current = match !items.is_empty() { @@ -329,7 +374,7 @@ impl IProfiles { Some(file) => dirs::app_profiles_dir()?.join(file), None => bail!("failed to get the file field"), }; - return help::read_merge_mapping(&file_path); + return help::read_mapping(&file_path); } bail!("failed to find the current profile \"uid:{current}\""); } @@ -364,4 +409,46 @@ impl IProfiles { _ => None, } } + + /// 获取current指向的订阅的rules + pub fn current_rules(&self) -> Option { + match (self.current.as_ref(), self.items.as_ref()) { + (Some(current), Some(items)) => { + if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) { + let rules = item.option.as_ref().and_then(|e| e.rules.clone()); + return rules; + } + None + } + _ => None, + } + } + + /// 获取current指向的订阅的proxies + pub fn current_proxies(&self) -> Option { + match (self.current.as_ref(), self.items.as_ref()) { + (Some(current), Some(items)) => { + if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) { + let proxies = item.option.as_ref().and_then(|e| e.proxies.clone()); + return proxies; + } + None + } + _ => None, + } + } + + /// 获取current指向的订阅的groups + pub fn current_groups(&self) -> Option { + match (self.current.as_ref(), self.items.as_ref()) { + (Some(current), Some(items)) => { + if let Some(item) = items.iter().find(|e| e.uid.as_ref() == Some(current)) { + let groups = item.option.as_ref().and_then(|e| e.groups.clone()); + return groups; + } + None + } + _ => None, + } + } } diff --git a/src-tauri/src/enhance/chain.rs b/src-tauri/src/enhance/chain.rs index bc88d42..2fb7e95 100644 --- a/src-tauri/src/enhance/chain.rs +++ b/src-tauri/src/enhance/chain.rs @@ -1,3 +1,4 @@ +use super::SeqMap; use crate::{ config::PrfItem, utils::{dirs, help}, @@ -15,6 +16,9 @@ pub struct ChainItem { pub enum ChainType { Merge(Mapping), Script(String), + Rules(SeqMap), + Proxies(SeqMap), + Groups(SeqMap), } #[derive(Debug, Clone)] @@ -43,7 +47,19 @@ impl From<&PrfItem> for Option { }), "merge" => Some(ChainItem { uid, - data: ChainType::Merge(help::read_merge_mapping(&path).ok()?), + data: ChainType::Merge(help::read_mapping(&path).ok()?), + }), + "rules" => Some(ChainItem { + uid, + data: ChainType::Rules(help::read_seq_map(&path).ok()?), + }), + "proxies" => Some(ChainItem { + uid, + data: ChainType::Proxies(help::read_seq_map(&path).ok()?), + }), + "groups" => Some(ChainItem { + uid, + data: ChainType::Groups(help::read_seq_map(&path).ok()?), }), _ => None, } diff --git a/src-tauri/src/enhance/mod.rs b/src-tauri/src/enhance/mod.rs index fc9d2c9..ee4f99c 100644 --- a/src-tauri/src/enhance/mod.rs +++ b/src-tauri/src/enhance/mod.rs @@ -2,12 +2,14 @@ mod chain; pub mod field; mod merge; mod script; +pub mod seq; mod tun; use self::chain::*; use self::field::*; use self::merge::*; use self::script::*; +use self::seq::*; use self::tun::*; use crate::config::Config; use crate::utils::tmpl; @@ -48,7 +50,7 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { }; // 从profiles里拿东西 - let (mut config, merge_item, script_item) = { + let (mut config, merge_item, script_item, rules_item, proxies_item, groups_item) = { let profiles = Config::profiles(); let profiles = profiles.latest(); @@ -59,9 +61,7 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { .and_then(>::from) .unwrap_or_else(|| ChainItem { uid: "".into(), - data: ChainType::Merge( - serde_yaml::from_str::(tmpl::ITEM_MERGE).unwrap_or_default(), - ), + data: ChainType::Merge(Mapping::new()), }); let script = profiles .get_item(&profiles.current_script().unwrap_or_default()) @@ -71,14 +71,56 @@ pub async fn enhance() -> (Mapping, Vec, HashMap) { uid: "".into(), data: ChainType::Script(tmpl::ITEM_SCRIPT.into()), }); + let rules = profiles + .get_item(&profiles.current_rules().unwrap_or_default()) + .ok() + .and_then(>::from) + .unwrap_or_else(|| ChainItem { + uid: "".into(), + data: ChainType::Rules(SeqMap::default()), + }); + let proxies = profiles + .get_item(&profiles.current_proxies().unwrap_or_default()) + .ok() + .and_then(>::from) + .unwrap_or_else(|| ChainItem { + uid: "".into(), + data: ChainType::Proxies(SeqMap::default()), + }); + let groups = profiles + .get_item(&profiles.current_groups().unwrap_or_default()) + .ok() + .and_then(>::from) + .unwrap_or_else(|| ChainItem { + uid: "".into(), + data: ChainType::Groups(SeqMap::default()), + }); - (current, merge, script) + (current, merge, script, rules, proxies, groups) }; let mut result_map = HashMap::new(); // 保存脚本日志 let mut exists_keys = use_keys(&config); // 保存出现过的keys // 处理用户的profile + match rules_item.data { + ChainType::Rules(rules) => { + config = use_seq(rules, config.to_owned(), "rules"); + } + _ => {} + } + match proxies_item.data { + ChainType::Proxies(proxies) => { + config = use_seq(proxies, config.to_owned(), "proxies"); + } + _ => {} + } + match groups_item.data { + ChainType::Groups(groups) => { + config = use_seq(groups, config.to_owned(), "proxy-groups"); + } + _ => {} + } match merge_item.data { ChainType::Merge(merge) => { exists_keys.extend(use_keys(&merge)); diff --git a/src-tauri/src/enhance/seq.rs b/src-tauri/src/enhance/seq.rs new file mode 100644 index 0000000..51c74f1 --- /dev/null +++ b/src-tauri/src/enhance/seq.rs @@ -0,0 +1,34 @@ +use serde::{Deserialize, Serialize}; +use serde_yaml::{Mapping, Sequence, Value}; +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct SeqMap { + prepend: Sequence, + append: Sequence, + delete: Sequence, +} + +pub fn use_seq(seq_map: SeqMap, config: Mapping, name: &str) -> Mapping { + let prepend = seq_map.prepend; + let append = seq_map.append; + let delete = seq_map.delete; + + let origin_seq = config.get(&name).map_or(Sequence::default(), |val| { + val.as_sequence().unwrap().clone() + }); + let mut seq = origin_seq.clone(); + + for item in prepend { + seq.insert(0, item); + } + + for item in append { + seq.push(item); + } + + for item in delete { + seq.retain(|x| x != &item); + } + let mut config = config.clone(); + config.insert(Value::from(name), Value::from(seq)); + return config; +} diff --git a/src-tauri/src/utils/help.rs b/src-tauri/src/utils/help.rs index a49eb1d..7c175de 100644 --- a/src-tauri/src/utils/help.rs +++ b/src-tauri/src/utils/help.rs @@ -1,3 +1,4 @@ +use crate::enhance::seq::SeqMap; use anyhow::{anyhow, bail, Context, Result}; use nanoid::nanoid; use serde::{de::DeserializeOwned, Serialize}; @@ -26,7 +27,7 @@ pub fn read_yaml(path: &PathBuf) -> Result { } /// read mapping from yaml fix #165 -pub fn read_merge_mapping(path: &PathBuf) -> Result { +pub fn read_mapping(path: &PathBuf) -> Result { let mut val: Value = read_yaml(path)?; val.apply_merge() .with_context(|| format!("failed to apply merge \"{}\"", path.display()))?; @@ -40,6 +41,13 @@ pub fn read_merge_mapping(path: &PathBuf) -> Result { .to_owned()) } +/// read mapping from yaml fix #165 +pub fn read_seq_map(path: &PathBuf) -> Result { + let val: SeqMap = read_yaml(path)?; + + Ok(val) +} + /// save the data to the file /// can set `prefix` string to add some comments pub fn save_yaml(path: &PathBuf, data: &T, prefix: Option<&str>) -> Result<()> { diff --git a/src-tauri/src/utils/tmpl.rs b/src-tauri/src/utils/tmpl.rs index b4b801a..45a018b 100644 --- a/src-tauri/src/utils/tmpl.rs +++ b/src-tauri/src/utils/tmpl.rs @@ -33,3 +33,33 @@ function main(config) { return config; } "; + +/// enhanced profile +pub const ITEM_RULES: &str = "# Profile Enhancement Rules Template for Clash Verge + +prepend: [] + +append: [] + +delete: [] +"; + +/// enhanced profile +pub const ITEM_PROXIES: &str = "# Profile Enhancement Proxies Template for Clash Verge + +prepend: [] + +append: [] + +delete: [] +"; + +/// enhanced profile +pub const ITEM_GROUPS: &str = "# Profile Enhancement Groups Template for Clash Verge + +prepend: [] + +append: [] + +delete: [] +"; diff --git a/src/components/profile/profile-item.tsx b/src/components/profile/profile-item.tsx index 9f392e4..7d659f8 100644 --- a/src/components/profile/profile-item.tsx +++ b/src/components/profile/profile-item.tsx @@ -105,6 +105,9 @@ export const ProfileItem = (props: Props) => { }, [hasUrl, updated]); const [fileOpen, setFileOpen] = useState(false); + const [rulesOpen, setRulesOpen] = useState(false); + const [proxiesOpen, setProxiesOpen] = useState(false); + const [groupsOpen, setGroupsOpen] = useState(false); const [mergeOpen, setMergeOpen] = useState(false); const [scriptOpen, setScriptOpen] = useState(false); const [confirmOpen, setConfirmOpen] = useState(false); @@ -124,6 +127,21 @@ export const ProfileItem = (props: Props) => { setFileOpen(true); }; + const onEditRules = () => { + setAnchorEl(null); + setRulesOpen(true); + }; + + const onEditProxies = () => { + setAnchorEl(null); + setProxiesOpen(true); + }; + + const onEditGroups = () => { + setAnchorEl(null); + setGroupsOpen(true); + }; + const onEditMerge = () => { setAnchorEl(null); setMergeOpen(true); @@ -191,6 +209,21 @@ export const ProfileItem = (props: Props) => { { label: "Select", handler: onForceSelect, disabled: false }, { label: "Edit Info", handler: onEditInfo, disabled: false }, { label: "Edit File", handler: onEditFile, disabled: false }, + { + label: "Edit Rules", + handler: onEditRules, + disabled: option?.rules === null, + }, + { + label: "Edit Proxies", + handler: onEditProxies, + disabled: option?.proxies === null, + }, + { + label: "Edit Groups", + handler: onEditGroups, + disabled: option?.groups === null, + }, { label: "Edit Merge", handler: onEditMerge, @@ -217,6 +250,21 @@ export const ProfileItem = (props: Props) => { { label: "Select", handler: onForceSelect, disabled: false }, { label: "Edit Info", handler: onEditInfo, disabled: false }, { label: "Edit File", handler: onEditFile, disabled: false }, + { + label: "Edit Rules", + handler: onEditRules, + disabled: option?.rules === null, + }, + { + label: "Edit Proxies", + handler: onEditProxies, + disabled: option?.proxies === null, + }, + { + label: "Edit Groups", + handler: onEditGroups, + disabled: option?.groups === null, + }, { label: "Edit Merge", handler: onEditMerge, @@ -435,7 +483,34 @@ export const ProfileItem = (props: Props) => { /> setRulesOpen(false)} + /> + setProxiesOpen(false)} + /> + setGroupsOpen(false)} + /> +