use crate::models::{ProxyGroupConfig, ProxyGroupType};
use serde::ser::SerializeMap;
use serde::{Serialize, Serializer};
use std::collections::HashMap;
pub fn serialize_proxy_group<S>(
group: &ProxyGroupConfig,
proxies: &[String],
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut map = serializer.serialize_map(None)?;
map.serialize_entry("name", &group.name)?;
let type_str = if group.group_type == ProxyGroupType::Smart {
"url-test"
} else {
group.type_str()
};
map.serialize_entry("type", &type_str)?;
match group.group_type {
ProxyGroupType::Select | ProxyGroupType::Relay => {
}
ProxyGroupType::LoadBalance => {
map.serialize_entry("strategy", &group.strategy_str())?;
if !group.lazy {
map.serialize_entry("lazy", &group.lazy)?;
}
map.serialize_entry("url", &group.url)?;
if group.interval > 0 {
map.serialize_entry("interval", &group.interval)?;
}
if group.tolerance > 0 {
map.serialize_entry("tolerance", &group.tolerance)?;
}
}
ProxyGroupType::Smart | ProxyGroupType::URLTest => {
if !group.lazy {
map.serialize_entry("lazy", &group.lazy)?;
}
map.serialize_entry("url", &group.url)?;
if group.interval > 0 {
map.serialize_entry("interval", &group.interval)?;
}
if group.tolerance > 0 {
map.serialize_entry("tolerance", &group.tolerance)?;
}
}
ProxyGroupType::Fallback => {
map.serialize_entry("url", &group.url)?;
if group.interval > 0 {
map.serialize_entry("interval", &group.interval)?;
}
if group.tolerance > 0 {
map.serialize_entry("tolerance", &group.tolerance)?;
}
}
ProxyGroupType::SSID => {
}
}
if group.disable_udp {
map.serialize_entry("disable-udp", &group.disable_udp)?;
}
if group.persistent {
map.serialize_entry("persistent", &group.persistent)?;
}
if group.evaluate_before_use {
map.serialize_entry("evaluate-before-use", &group.evaluate_before_use)?;
}
if !group.using_provider.is_empty() {
let provider_seq: Vec<&String> = group.using_provider.iter().collect();
map.serialize_entry("use", &provider_seq)?;
} else {
if !proxies.is_empty() {
map.serialize_entry("proxies", &proxies)?;
} else {
map.serialize_entry("proxies", &["DIRECT"])?;
}
}
map.end()
}
#[derive(Debug, Serialize)]
pub struct ClashProxyGroup {
pub name: String,
#[serde(rename = "type")]
pub group_type: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub proxies: Vec<String>,
#[serde(rename = "use", skip_serializing_if = "Vec::is_empty")]
pub using_provider: Vec<String>,
#[serde(skip_serializing_if = "String::is_empty")]
pub url: String,
#[serde(skip_serializing_if = "is_zero_u32")]
pub interval: u32,
#[serde(skip_serializing_if = "is_zero_u32")]
pub timeout: u32,
#[serde(skip_serializing_if = "is_zero_u32")]
pub tolerance: u32,
#[serde(skip_serializing_if = "String::is_empty")]
pub strategy: String,
#[serde(skip_serializing_if = "is_true")]
pub lazy: bool,
#[serde(rename = "disable-udp", skip_serializing_if = "is_false")]
pub disable_udp: bool,
#[serde(skip_serializing_if = "is_false")]
pub persistent: bool,
#[serde(rename = "evaluate-before-use", skip_serializing_if = "is_false")]
pub evaluate_before_use: bool,
}
fn is_zero_u32(val: &u32) -> bool {
*val == 0
}
fn is_true(val: &bool) -> bool {
*val
}
fn is_false(val: &bool) -> bool {
!*val
}
impl From<&ProxyGroupConfig> for ClashProxyGroup {
fn from(config: &ProxyGroupConfig) -> Self {
let type_str = if config.group_type == ProxyGroupType::Smart {
"url-test".to_string()
} else {
config.type_str().to_string()
};
let mut clash_group = ClashProxyGroup {
name: config.name.clone(),
group_type: type_str,
proxies: config.proxies.clone(),
using_provider: config.using_provider.clone(),
url: String::new(),
interval: 0,
timeout: 0,
tolerance: 0,
strategy: String::new(),
lazy: true, disable_udp: config.disable_udp,
persistent: config.persistent,
evaluate_before_use: config.evaluate_before_use,
};
match config.group_type {
ProxyGroupType::LoadBalance => {
clash_group.strategy = config.strategy_str().to_string();
clash_group.lazy = config.lazy;
clash_group.url = config.url.clone();
clash_group.interval = config.interval;
clash_group.tolerance = config.tolerance;
}
ProxyGroupType::URLTest | ProxyGroupType::Smart | ProxyGroupType::Fallback => {
clash_group.url = config.url.clone();
clash_group.interval = config.interval;
clash_group.tolerance = config.tolerance;
if matches!(
config.group_type,
ProxyGroupType::URLTest | ProxyGroupType::Smart
) {
clash_group.lazy = config.lazy;
}
}
_ => {} }
if clash_group.proxies.is_empty() && clash_group.using_provider.is_empty() {
clash_group.proxies = vec!["DIRECT".to_string()];
}
clash_group
}
}
pub fn convert_proxy_groups(
group_configs: &[ProxyGroupConfig],
filtered_nodes_map: Option<&HashMap<String, Vec<String>>>,
) -> Vec<ClashProxyGroup> {
let mut clash_groups = Vec::with_capacity(group_configs.len());
for group in group_configs {
let mut clash_group = ClashProxyGroup::from(group);
if let Some(filtered_map) = filtered_nodes_map {
if let Some(filtered_nodes) = filtered_map.get(&group.name) {
clash_group.proxies = filtered_nodes.clone();
if clash_group.proxies.is_empty() && clash_group.using_provider.is_empty() {
clash_group.proxies = vec!["DIRECT".to_string()];
}
}
}
clash_groups.push(clash_group);
}
clash_groups
}
pub fn example_clash_groups() -> Vec<ClashProxyGroup> {
let mut groups = Vec::new();
let mut select_group = ProxyGroupConfig::new("Proxy".to_string(), ProxyGroupType::Select);
select_group.proxies = vec![
"Hong Kong".to_string(),
"Singapore".to_string(),
"US".to_string(),
];
groups.push(select_group);
let mut urltest_group = ProxyGroupConfig::new("Auto".to_string(), ProxyGroupType::URLTest);
urltest_group.url = "http://www.gstatic.com/generate_204".to_string();
urltest_group.interval = 300;
urltest_group.proxies = vec!["Hong Kong".to_string(), "Singapore".to_string()];
groups.push(urltest_group);
let mut fallback_group =
ProxyGroupConfig::new("Fallback".to_string(), ProxyGroupType::Fallback);
fallback_group.url = "http://www.gstatic.com/generate_204".to_string();
fallback_group.interval = 300;
fallback_group.proxies = vec![
"Hong Kong".to_string(),
"Singapore".to_string(),
"US".to_string(),
];
groups.push(fallback_group);
convert_proxy_groups(&groups, None)
}