refactor: Associate Profile with Merge/Script.

This commit is contained in:
MystiPanda
2024-06-29 23:07:44 +08:00
parent b85929772e
commit 3efef52398
15 changed files with 286 additions and 613 deletions

View File

@@ -94,6 +94,16 @@ pub struct PrfOption {
/// default is `false`
#[serde(skip_serializing_if = "Option::is_none")]
pub danger_accept_invalid_certs: Option<bool>,
pub merge: Option<String>,
pub script: Option<String>,
pub rules: Option<String>,
pub proxies: Option<String>,
pub groups: Option<String>,
}
impl PrfOption {
@@ -107,6 +117,11 @@ impl PrfOption {
.danger_accept_invalid_certs
.or(a.danger_accept_invalid_certs);
a.update_interval = b.update_interval.or(a.update_interval);
a.merge = b.merge.or(a.merge);
a.script = b.script.or(a.script);
a.rules = b.rules.or(a.rules);
a.proxies = b.proxies.or(a.proxies);
a.groups = b.groups.or(a.groups);
Some(a)
}
t => t.0.or(t.1),
@@ -137,16 +152,8 @@ impl PrfItem {
let desc = item.desc.unwrap_or("".into());
PrfItem::from_local(name, desc, file_data, item.option)
}
"merge" => {
let name = item.name.unwrap_or("Merge".into());
let desc = item.desc.unwrap_or("".into());
PrfItem::from_merge(name, desc)
}
"script" => {
let name = item.name.unwrap_or("Script".into());
let desc = item.desc.unwrap_or("".into());
PrfItem::from_script(name, desc)
}
"merge" => PrfItem::from_merge(),
"script" => PrfItem::from_script(),
typ => bail!("invalid profile item type \"{typ}\""),
}
}
@@ -161,7 +168,24 @@ impl PrfItem {
) -> Result<PrfItem> {
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());
if merge.is_none() {
let merge_item = PrfItem::from_merge()?;
Config::profiles().data().append_item(merge_item.clone())?;
merge = merge_item.uid;
}
if script.is_none() {
let script_item = PrfItem::from_script()?;
Config::profiles().data().append_item(script_item.clone())?;
script = script_item.uid;
}
Ok(PrfItem {
uid: Some(uid),
itype: Some("local".into()),
@@ -172,7 +196,12 @@ impl PrfItem {
selected: None,
extra: None,
option: Some(PrfOption {
update_interval: option.unwrap_or_default().update_interval,
update_interval,
merge,
script,
rules,
proxies,
groups,
..PrfOption::default()
}),
home: None,
@@ -196,9 +225,23 @@ impl PrfItem {
opt_ref.map_or(false, |o| o.danger_accept_invalid_certs.unwrap_or(false));
let user_agent = opt_ref.and_then(|o| o.user_agent.clone());
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 builder = reqwest::ClientBuilder::new().use_rustls_tls().no_proxy();
if merge.is_none() {
let merge_item = PrfItem::from_merge()?;
Config::profiles().data().append_item(merge_item.clone())?;
merge = merge_item.uid;
}
if script.is_none() {
let script_item = PrfItem::from_script()?;
Config::profiles().data().append_item(script_item.clone())?;
script = script_item.uid;
}
// 使用软件自己的代理
if self_proxy {
let port = Config::verge()
@@ -290,17 +333,11 @@ impl PrfItem {
crate::utils::help::get_last_part_and_decode(url).unwrap_or("Remote File".into()),
),
};
let option = match update_interval {
Some(val) => Some(PrfOption {
update_interval: Some(val),
..PrfOption::default()
}),
let update_interval = match update_interval {
Some(val) => Some(val),
None => match header.get("profile-update-interval") {
Some(value) => match value.to_str().unwrap_or("").parse::<u64>() {
Ok(val) => Some(PrfOption {
update_interval: Some(val * 60), // hour -> min
..PrfOption::default()
}),
Ok(val) => Some(val * 60), // hour -> min
Err(_) => None,
},
None => None,
@@ -340,7 +377,15 @@ impl PrfItem {
url: Some(url.into()),
selected: None,
extra,
option,
option: Some(PrfOption {
update_interval,
merge,
script,
rules,
proxies,
groups,
..PrfOption::default()
}),
home,
updated: Some(chrono::Local::now().timestamp() as usize),
file_data: Some(data.into()),
@@ -349,15 +394,15 @@ impl PrfItem {
/// ## Merge type (enhance)
/// create the enhanced item by using `merge` rule
pub fn from_merge(name: String, desc: String) -> Result<PrfItem> {
pub fn from_merge() -> Result<PrfItem> {
let uid = help::get_uid("m");
let file = format!("{uid}.yaml");
Ok(PrfItem {
uid: Some(uid),
itype: Some("merge".into()),
name: Some(name),
desc: Some(desc),
name: None,
desc: None,
file: Some(file),
url: None,
selected: None,
@@ -371,15 +416,15 @@ impl PrfItem {
/// ## Script type (enhance)
/// create the enhanced item by using javascript quick.js
pub fn from_script(name: String, desc: String) -> Result<PrfItem> {
pub fn from_script() -> Result<PrfItem> {
let uid = help::get_uid("s");
let file = format!("{uid}.js"); // js ext
Ok(PrfItem {
uid: Some(uid),
itype: Some("script".into()),
name: Some(name),
desc: Some(desc),
name: None,
desc: None,
file: Some(file),
url: None,
home: None,

View File

@@ -11,9 +11,6 @@ pub struct IProfiles {
/// same as PrfConfig.current
pub current: Option<String>,
/// same as PrfConfig.chain
pub chain: Option<Vec<String>>,
/// profile list
pub items: Option<Vec<PrfItem>>,
}
@@ -80,10 +77,6 @@ impl IProfiles {
}
}
if let Some(chain) = patch.chain {
self.chain = Some(chain);
}
Ok(())
}
@@ -243,9 +236,19 @@ impl IProfiles {
pub fn delete_item(&mut self, uid: String) -> Result<bool> {
let current = self.current.as_ref().unwrap_or(&uid);
let current = current.clone();
let item = self.get_item(&uid)?;
let merge_uid = item.option.as_ref().and_then(|e| e.merge.clone());
let script_uid = item.option.as_ref().and_then(|e| e.script.clone());
let rules_uid = item.option.as_ref().and_then(|e| e.rules.clone());
let proxies_uid = item.option.as_ref().and_then(|e| e.proxies.clone());
let groups_uid = item.option.as_ref().and_then(|e| e.groups.clone());
let mut items = self.items.take().unwrap_or_default();
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;
// get the index
for (i, _) in items.iter().enumerate() {
@@ -266,6 +269,44 @@ impl IProfiles {
}
}
// get the merge index
for (i, _) in items.iter().enumerate() {
if items[i].uid == merge_uid {
merge_index = Some(i);
break;
}
}
if let Some(index) = merge_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 script index
for (i, _) in items.iter().enumerate() {
if items[i].uid == script_uid {
script_index = Some(i);
break;
}
}
if let Some(index) = script_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() {
@@ -295,4 +336,32 @@ impl IProfiles {
_ => Ok(Mapping::new()),
}
}
/// 获取current指向的订阅的merge
pub fn current_merge(&self) -> Option<String> {
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 merge = item.option.as_ref().and_then(|e| e.merge.clone());
return merge;
}
None
}
_ => None,
}
}
/// 获取current指向的订阅的script
pub fn current_script(&self) -> Option<String> {
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 script = item.option.as_ref().and_then(|e| e.script.clone());
return script;
}
None
}
_ => None,
}
}
}

View File

@@ -10,6 +10,7 @@ use self::merge::*;
use self::script::*;
use self::tun::*;
use crate::config::Config;
use crate::utils::tmpl;
use serde_yaml::Mapping;
use std::collections::HashMap;
use std::collections::HashSet;
@@ -47,33 +48,45 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
};
// 从profiles里拿东西
let (mut config, chain) = {
let (mut config, merge_item, script_item) = {
let profiles = Config::profiles();
let profiles = profiles.latest();
let current = profiles.current_mapping().unwrap_or_default();
let merge = profiles
.get_item(&profiles.current_merge().unwrap_or_default())
.ok()
.and_then(<Option<ChainItem>>::from)
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Merge(
serde_yaml::from_str::<Mapping>(tmpl::ITEM_MERGE).unwrap_or_default(),
),
});
let script = profiles
.get_item(&profiles.current_script().unwrap_or_default())
.ok()
.and_then(<Option<ChainItem>>::from)
.unwrap_or_else(|| ChainItem {
uid: "".into(),
data: ChainType::Script(tmpl::ITEM_SCRIPT.into()),
});
let chain = match profiles.chain.as_ref() {
Some(chain) => chain
.iter()
.filter_map(|uid| profiles.get_item(uid).ok())
.filter_map(<Option<ChainItem>>::from)
.collect::<Vec<ChainItem>>(),
None => vec![],
};
(current, chain)
(current, merge, script)
};
let mut result_map = HashMap::new(); // 保存脚本日志
let mut exists_keys = use_keys(&config); // 保存出现过的keys
// 处理用户的profile
chain.into_iter().for_each(|item| match item.data {
match merge_item.data {
ChainType::Merge(merge) => {
exists_keys.extend(use_keys(&merge));
config = use_merge(merge, config.to_owned());
}
_ => {}
}
match script_item.data {
ChainType::Script(script) => {
let mut logs = vec![];
@@ -86,9 +99,10 @@ pub async fn enhance() -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
Err(err) => logs.push(("exception".into(), err.to_string())),
}
result_map.insert(item.uid, logs);
result_map.insert(script_item.uid, logs);
}
});
_ => {}
}
// 合并默认的config
for (key, value) in clash_config.into_iter() {

View File

@@ -292,7 +292,6 @@ pub async fn update_profile(uid: String, option: Option<PrfOption>) -> Result<()
Some((url, opt)) => {
let merged_opt = PrfOption::merge(opt, option);
let item = PrfItem::from_url(&url, None, None, merged_opt).await?;
let profiles = Config::profiles();
let mut profiles = profiles.latest();
profiles.update_item(uid.clone(), item)?;

View File

@@ -1,10 +1,6 @@
use crate::config::{IVerge, PrfOption};
use crate::{
config::{Config, PrfItem},
core::*,
utils::init,
utils::server,
};
use crate::cmds::import_profile;
use crate::config::IVerge;
use crate::{config::Config, core::*, utils::init, utils::server};
use crate::{log_err, trace_err};
use anyhow::Result;
use once_cell::sync::OnceCell;
@@ -102,7 +98,7 @@ pub async fn resolve_setup(app: &mut App) {
if argvs.len() > 1 {
let param = argvs[1].as_str();
if param.starts_with("clash:") {
resolve_scheme(argvs[1].to_owned()).await;
log_err!(resolve_scheme(argvs[1].to_owned()).await);
}
}
}
@@ -240,31 +236,26 @@ pub fn save_window_size_position(app_handle: &AppHandle, save_to_file: bool) ->
Ok(())
}
pub async fn resolve_scheme(param: String) {
pub async fn resolve_scheme(param: String) -> Result<()> {
let url = param
.trim_start_matches("clash://install-config/?url=")
.trim_start_matches("clash://install-config?url=");
let option = PrfOption {
user_agent: None,
with_proxy: Some(true),
self_proxy: None,
danger_accept_invalid_certs: None,
update_interval: None,
};
if let Ok(item) = PrfItem::from_url(url, None, None, Some(option)).await {
if Config::profiles().data().append_item(item).is_ok() {
match import_profile(url.to_string(), None).await {
Ok(_) => {
notification::Notification::new(crate::utils::dirs::APP_ID)
.title("Clash Verge")
.body("Import profile success")
.show()
.unwrap();
};
} else {
notification::Notification::new(crate::utils::dirs::APP_ID)
.title("Clash Verge")
.body("Import profile failed")
.show()
.unwrap();
log::error!("failed to parse url: {}", url);
}
Err(e) => {
notification::Notification::new(crate::utils::dirs::APP_ID)
.title("Clash Verge")
.body(format!("Import profile failed: {e}"))
.show()
.unwrap();
log::error!("Import profile failed: {e}");
}
}
Ok(())
}

View File

@@ -1,7 +1,10 @@
extern crate warp;
use super::resolve;
use crate::config::{Config, IVerge, DEFAULT_PAC};
use crate::{
config::{Config, IVerge, DEFAULT_PAC},
log_err,
};
use anyhow::{bail, Result};
use port_scanner::local_port_available;
use std::convert::Infallible;
@@ -85,7 +88,7 @@ pub fn embed_server(app_handle: AppHandle) {
.and_then(scheme_handler);
async fn scheme_handler(query: QueryParam) -> Result<impl warp::Reply, Infallible> {
resolve::resolve_scheme(query.param).await;
log_err!(resolve::resolve_scheme(query.param).await);
Ok("ok")
}
let commands = ping.or(visible).or(pac).or(scheme);