use crate::generator::config::group::group_generate;
use crate::generator::config::remark::process_remark;
use crate::generator::ruleconvert::ruleset_to_clash_str;
use crate::generator::yaml::clash::clash_output::ClashProxyOutput;
use crate::generator::yaml::proxy_group_output::convert_proxy_groups;
use crate::models::{ExtraSettings, Proxy, ProxyGroupConfigs, ProxyType, RulesetContent};
use log::error;
use serde_yaml::{self, Mapping, Sequence, Value as YamlValue};
use std::collections::{HashMap, HashSet};
lazy_static::lazy_static! {
static ref CLASH_SSR_CIPHERS: HashSet<&'static str> = {
let mut ciphers = HashSet::new();
ciphers.insert("aes-128-cfb");
ciphers.insert("aes-192-cfb");
ciphers.insert("aes-256-cfb");
ciphers.insert("aes-128-ctr");
ciphers.insert("aes-192-ctr");
ciphers.insert("aes-256-ctr");
ciphers.insert("aes-128-ofb");
ciphers.insert("aes-192-ofb");
ciphers.insert("aes-256-ofb");
ciphers.insert("des-cfb");
ciphers.insert("bf-cfb");
ciphers.insert("cast5-cfb");
ciphers.insert("rc4-md5");
ciphers.insert("chacha20");
ciphers.insert("chacha20-ietf");
ciphers.insert("salsa20");
ciphers.insert("camellia-128-cfb");
ciphers.insert("camellia-192-cfb");
ciphers.insert("camellia-256-cfb");
ciphers.insert("idea-cfb");
ciphers.insert("rc2-cfb");
ciphers.insert("seed-cfb");
ciphers
};
static ref CLASHR_PROTOCOLS: HashSet<&'static str> = {
let mut protocols = HashSet::new();
protocols.insert("origin");
protocols.insert("auth_sha1_v4");
protocols.insert("auth_aes128_md5");
protocols.insert("auth_aes128_sha1");
protocols.insert("auth_chain_a");
protocols.insert("auth_chain_b");
protocols
};
static ref CLASHR_OBFS: HashSet<&'static str> = {
let mut obfs = HashSet::new();
obfs.insert("plain");
obfs.insert("http_simple");
obfs.insert("http_post");
obfs.insert("random_head");
obfs.insert("tls1.2_ticket_auth");
obfs.insert("tls1.2_ticket_fastauth");
obfs
};
}
pub fn proxy_to_clash(
nodes: &mut Vec<Proxy>,
base_conf: &str,
ruleset_content_array: &mut Vec<RulesetContent>,
extra_proxy_group: &ProxyGroupConfigs,
clash_r: bool,
ext: &mut ExtraSettings,
) -> String {
let mut yaml_node: YamlValue = match serde_yaml::from_str(base_conf) {
Ok(node) => node,
Err(e) => {
error!("Clash base loader failed with error: {}", e);
return String::new();
}
};
if yaml_node.is_null() {
yaml_node = YamlValue::Mapping(Mapping::new());
}
proxy_to_clash_yaml(
nodes,
&mut yaml_node,
ruleset_content_array,
extra_proxy_group,
clash_r,
ext,
);
if ext.nodelist {
return match serde_yaml::to_string(&yaml_node) {
Ok(result) => result,
Err(_) => String::new(),
};
}
if !ext.enable_rule_generator {
return match serde_yaml::to_string(&yaml_node) {
Ok(result) => result,
Err(_) => String::new(),
};
}
if !ext.managed_config_prefix.is_empty() || ext.clash_script {
if yaml_node.get("mode").is_some() {
if let Some(ref mut map) = yaml_node.as_mapping_mut() {
map.insert(
YamlValue::String("mode".to_string()),
YamlValue::String(
if ext.clash_script {
if ext.clash_new_field_name {
"script"
} else {
"Script"
}
} else {
if ext.clash_new_field_name {
"rule"
} else {
"Rule"
}
}
.to_string(),
),
);
}
}
return match serde_yaml::to_string(&yaml_node) {
Ok(result) => result,
Err(_) => String::new(),
};
}
let rules_str = ruleset_to_clash_str(
&yaml_node,
ruleset_content_array,
ext.overwrite_original_rules,
ext.clash_new_field_name,
);
let yaml_output = match serde_yaml::to_string(&yaml_node) {
Ok(result) => result,
Err(_) => String::new(),
};
format!("{}{}", yaml_output, rules_str)
}
pub fn proxy_to_clash_yaml(
nodes: &mut Vec<Proxy>,
yaml_node: &mut serde_yaml::Value,
_ruleset_content_array: &Vec<RulesetContent>,
extra_proxy_group: &ProxyGroupConfigs,
clash_r: bool,
ext: &mut ExtraSettings,
) {
let _proxy_block = ext.clash_proxies_style == "block";
let _proxy_compact = ext.clash_proxies_style == "compact";
let _group_block = ext.clash_proxy_groups_style == "block";
let _group_compact = ext.clash_proxy_groups_style == "compact";
let mut proxies_json = Vec::new();
let mut remarks_list = Vec::new();
for node in nodes.iter_mut() {
let mut remark = node.remark.clone();
if ext.append_proxy_type {
remark = format!("[{}] {}", node.proxy_type.to_string(), remark);
}
process_remark(&mut remark, &remarks_list, false);
remarks_list.push(remark.clone());
let should_skip = match node.proxy_type {
ProxyType::Snell if node.snell_version >= 4 => true,
ProxyType::ShadowsocksR if !clash_r && ext.filter_deprecated => true,
ProxyType::Shadowsocks
if ext.filter_deprecated && node.encrypt_method.as_deref() == Some("chacha20") =>
{
true
}
ProxyType::ShadowsocksR if ext.filter_deprecated => {
let encrypt_method = node.encrypt_method.as_deref().unwrap_or("");
let protocol = node.protocol.as_deref().unwrap_or("");
let obfs = node.obfs.as_deref().unwrap_or("");
!CLASH_SSR_CIPHERS.contains(encrypt_method)
|| !CLASHR_PROTOCOLS.contains(protocol)
|| !CLASHR_OBFS.contains(obfs)
}
ProxyType::Unknown | ProxyType::HTTPS => true,
_ => false,
};
if should_skip {
continue;
}
let proxy_copy = node.clone().set_remark(remark).apply_default_values(
ext.udp,
ext.tfo,
ext.skip_cert_verify,
);
let clash_proxy = ClashProxyOutput::from(proxy_copy);
proxies_json.push(clash_proxy);
}
if ext.nodelist {
let mut provider = YamlValue::Mapping(Mapping::new());
provider["proxies"] =
serde_yaml::to_value(&proxies_json).unwrap_or(YamlValue::Sequence(Vec::new()));
*yaml_node = provider;
return;
}
if let Some(ref mut map) = yaml_node.as_mapping_mut() {
let proxies_yaml_value =
serde_yaml::to_value(&proxies_json).unwrap_or(YamlValue::Sequence(Vec::new()));
if ext.clash_new_field_name {
map.insert(YamlValue::String("proxies".to_string()), proxies_yaml_value);
} else {
map.insert(YamlValue::String("Proxy".to_string()), proxies_yaml_value);
}
}
if !extra_proxy_group.is_empty() {
let mut original_groups = if ext.clash_new_field_name {
match yaml_node.get("proxy-groups") {
Some(YamlValue::Sequence(seq)) => seq.clone(),
_ => Sequence::new(),
}
} else {
match yaml_node.get("Proxy Group") {
Some(YamlValue::Sequence(seq)) => seq.clone(),
_ => Sequence::new(),
}
};
let mut filtered_nodes_map = HashMap::new();
for group in extra_proxy_group {
let mut filtered_nodes = Vec::new();
for proxy_name in &group.proxies {
group_generate(proxy_name, nodes, &mut filtered_nodes, true, ext);
}
if filtered_nodes.is_empty() && group.using_provider.is_empty() {
filtered_nodes.push("DIRECT".to_string());
}
filtered_nodes_map.insert(group.name.clone(), filtered_nodes);
}
let clash_proxy_groups = convert_proxy_groups(extra_proxy_group, Some(&filtered_nodes_map));
for group in clash_proxy_groups {
let mut replaced = false;
for i in 0..original_groups.len() {
if let Some(YamlValue::Mapping(map)) = original_groups.get(i) {
if let Some(YamlValue::String(name)) =
map.get(&YamlValue::String("name".to_string()))
{
if name == &group.name {
if let Some(elem) = original_groups.get_mut(i) {
if let Ok(group_yaml) = serde_yaml::to_value(&group) {
*elem = group_yaml;
replaced = true;
break;
}
}
}
}
}
}
if !replaced {
if let Ok(group_yaml) = serde_yaml::to_value(&group) {
original_groups.push(group_yaml);
}
}
}
if let Some(ref mut map) = yaml_node.as_mapping_mut() {
if ext.clash_new_field_name {
map.insert(
YamlValue::String("proxy-groups".to_string()),
YamlValue::Sequence(original_groups),
);
} else {
map.insert(
YamlValue::String("Proxy Group".to_string()),
YamlValue::Sequence(original_groups),
);
}
}
}
}