mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 08:45:41 +08:00
- Replaced direct log calls with a new logging macro that includes a logging type for better categorization. - Updated logging in various modules including `merge.rs`, `mod.rs`, `tun.rs`, `clash.rs`, `profile.rs`, `proxy.rs`, `window.rs`, `lightweight.rs`, `guard.rs`, `autostart.rs`, `dirs.rs`, `dns.rs`, `scheme.rs`, `server.rs`, and `window_manager.rs`. - Introduced logging types such as `Core`, `Network`, `ProxyMode`, `Window`, `Lightweight`, `Service`, and `File` to enhance log clarity and filtering.
549 lines
16 KiB
Rust
549 lines
16 KiB
Rust
mod chain;
|
||
pub mod field;
|
||
mod merge;
|
||
mod script;
|
||
pub mod seq;
|
||
mod tun;
|
||
|
||
use self::{chain::*, field::*, merge::*, script::*, seq::*, tun::*};
|
||
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<String>,
|
||
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,
|
||
}
|
||
|
||
async fn get_config_values() -> ConfigValues {
|
||
let clash_config = { Config::clash().await.latest_ref().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_ref();
|
||
(
|
||
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_ref();
|
||
verge.verge_redir_enabled.unwrap_or(false)
|
||
};
|
||
|
||
#[cfg(target_os = "linux")]
|
||
let tproxy_enabled = {
|
||
let verge = Config::verge().await;
|
||
let verge = verge.latest_ref();
|
||
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,
|
||
}
|
||
}
|
||
|
||
async fn collect_profile_items() -> ProfileItems {
|
||
// 从profiles里拿东西 - 先收集需要的数据,然后释放锁
|
||
let (
|
||
current,
|
||
merge_uid,
|
||
script_uid,
|
||
rules_uid,
|
||
proxies_uid,
|
||
groups_uid,
|
||
_current_profile_uid,
|
||
name,
|
||
) = {
|
||
let current = {
|
||
let profiles = Config::profiles().await;
|
||
let profiles_clone = profiles.latest_ref().clone();
|
||
profiles_clone.current_mapping().await.unwrap_or_default()
|
||
};
|
||
|
||
let profiles = Config::profiles().await;
|
||
let profiles_ref = profiles.latest_ref();
|
||
|
||
let merge_uid = profiles_ref.current_merge().unwrap_or_default();
|
||
let script_uid = profiles_ref.current_script().unwrap_or_default();
|
||
let rules_uid = profiles_ref.current_rules().unwrap_or_default();
|
||
let proxies_uid = profiles_ref.current_proxies().unwrap_or_default();
|
||
let groups_uid = profiles_ref.current_groups().unwrap_or_default();
|
||
let current_profile_uid = profiles_ref.get_current().unwrap_or_default();
|
||
|
||
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,
|
||
current_profile_uid,
|
||
name,
|
||
)
|
||
};
|
||
|
||
// 现在获取具体的items,此时profiles锁已经释放
|
||
let merge_item = {
|
||
let item = {
|
||
let profiles = Config::profiles().await;
|
||
let profiles = profiles.latest_ref();
|
||
profiles.get_item(merge_uid).ok().cloned()
|
||
};
|
||
if let Some(item) = item {
|
||
<Option<ChainItem>>::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_ref();
|
||
profiles.get_item(script_uid).ok().cloned()
|
||
};
|
||
if let Some(item) = item {
|
||
<Option<ChainItem>>::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_ref();
|
||
profiles.get_item(rules_uid).ok().cloned()
|
||
};
|
||
if let Some(item) = item {
|
||
<Option<ChainItem>>::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_ref();
|
||
profiles.get_item(proxies_uid).ok().cloned()
|
||
};
|
||
if let Some(item) = item {
|
||
<Option<ChainItem>>::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_ref();
|
||
profiles.get_item(groups_uid).ok().cloned()
|
||
};
|
||
if let Some(item) = item {
|
||
<Option<ChainItem>>::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_ref();
|
||
profiles.get_item("Merge").ok().cloned()
|
||
};
|
||
if let Some(item) = item {
|
||
<Option<ChainItem>>::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_ref();
|
||
profiles.get_item("Script").ok().cloned()
|
||
};
|
||
if let Some(item) = item {
|
||
<Option<ChainItem>>::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<String>, HashMap<String, ResultLog>) {
|
||
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.to_owned()) {
|
||
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<String>,
|
||
mut result_map: HashMap<String, ResultLog>,
|
||
rules_item: ChainItem,
|
||
proxies_item: ChainItem,
|
||
groups_item: ChainItem,
|
||
merge_item: ChainItem,
|
||
script_item: ChainItem,
|
||
profile_name: String,
|
||
) -> (Mapping, Vec<String>, HashMap<String, ResultLog>) {
|
||
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") || key.as_str() == Some("tproxy-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;
|
||
}
|
||
}
|
||
// 处理 external-controller 键的开关逻辑
|
||
if key.as_str() == Some("external-controller") {
|
||
let enable_external_controller = Config::verge()
|
||
.await
|
||
.latest_ref()
|
||
.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<String>,
|
||
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("dns_config.yaml");
|
||
|
||
if dns_path.exists()
|
||
&& let Ok(dns_yaml) = fs::read_to_string(&dns_path).await
|
||
&& let Ok(dns_config) = serde_yaml_ng::from_str::<serde_yaml_ng::Mapping>(&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<String>, HashMap<String, ResultLog>) {
|
||
// 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<String> = exists_set.into_iter().collect();
|
||
|
||
(config, exists_keys, result_map)
|
||
}
|