use crate::generator::config::group::group_generate;
use crate::generator::config::remark::process_remark;
use crate::generator::ruleconvert::ruleset_to_surge::ruleset_to_surge;
use crate::models::{
ExtraSettings, Proxy, ProxyGroupConfigs, ProxyGroupType, ProxyType, RulesetContent,
};
use crate::utils::base64::url_safe_base64_encode;
use crate::utils::ini_reader::IniReader;
use crate::utils::string::{hash, join};
use crate::utils::tribool::BoolTriboolExt;
use crate::utils::url::url_encode;
use log::error;
pub async fn proxy_to_mellow(
nodes: &mut Vec<Proxy>,
base_conf: &str,
ruleset_content_array: &mut Vec<RulesetContent>,
extra_proxy_group: &ProxyGroupConfigs,
ext: &mut ExtraSettings,
) -> String {
let mut ini = IniReader::new();
ini.store_any_line = true;
if ini.parse(base_conf).is_err() {
error!(
"Mellow base loader failed with error: {}",
ini.get_last_error()
);
return String::new();
}
proxy_to_mellow_internal(
nodes,
&mut ini,
ruleset_content_array,
extra_proxy_group,
ext,
)
.await;
ini.to_string()
}
async fn proxy_to_mellow_internal(
nodes: &mut Vec<Proxy>,
ini: &mut IniReader,
ruleset_content_array: &mut Vec<RulesetContent>,
extra_proxy_group: &ProxyGroupConfigs,
ext: &mut ExtraSettings,
) {
let mut nodelist = Vec::new();
let mut remarks_list = Vec::new();
ini.set_current_section("Endpoint");
for node in nodes {
if ext.append_proxy_type {
let proxy_type = node.proxy_type.to_string();
node.remark = format!("[{}] {}", proxy_type, node.remark);
}
let mut remark = node.remark.clone();
process_remark(&mut remark, &remarks_list, true);
node.remark = remark;
let hostname = &node.hostname;
let port = node.port.to_string();
let username = node.username.as_deref().unwrap_or("");
let password = node.password.as_deref().unwrap_or("");
let method = node.encrypt_method.as_deref().unwrap_or("");
let id = node.user_id.as_deref().unwrap_or("");
let transproto = node.transfer_protocol.as_deref().unwrap_or("");
let host = node.host.as_deref().unwrap_or("");
let path = node.path.as_deref().unwrap_or("");
let quicsecure = node.quic_secure.as_deref().unwrap_or("");
let quicsecret = node.quic_secret.as_deref().unwrap_or("");
let plugin = node.plugin.as_deref().unwrap_or("");
let tls_secure = if node.tls_secure { "true" } else { "false" };
let mut tfo = ext.tfo;
let mut scv = ext.skip_cert_verify;
tfo = node.tcp_fast_open.as_ref().map_or(tfo, |val| Some(*val));
scv = node.allow_insecure.as_ref().map_or(scv, |val| Some(*val));
let mut _proxy_str: String = String::new();
match node.proxy_type {
ProxyType::Shadowsocks => {
if !plugin.is_empty() {
continue;
}
_proxy_str = format!(
"{}, ss, ss://{}/{}:{}",
node.remark,
url_safe_base64_encode(&format!("{}:{}", method, password)),
hostname,
port
);
}
ProxyType::VMess => {
_proxy_str = format!(
"{}, vmess1, vmess1://{}@{}:{}",
node.remark, id, hostname, port
);
if !path.is_empty() {
_proxy_str.push_str(path);
}
_proxy_str.push_str(&format!("?network={}", transproto));
match hash(transproto) {
h if h == hash("ws") => {
_proxy_str.push_str(&format!("&ws.host={}", url_encode(host)));
}
h if h == hash("http") => {
if !host.is_empty() {
_proxy_str.push_str(&format!("&http.host={}", url_encode(host)));
}
}
h if h == hash("quic") => {
if !quicsecure.is_empty() {
_proxy_str.push_str(&format!(
"&quic.security={}&quic.key={}",
quicsecure, quicsecret
));
}
}
h if h == hash("kcp") || h == hash("tcp") => {
}
_ => {}
}
_proxy_str.push_str(&format!("&tls={}", tls_secure));
if tls_secure == "true" {
if !host.is_empty() {
_proxy_str.push_str(&format!("&tls.servername={}", url_encode(host)));
}
}
if !scv.is_undef() {
_proxy_str.push_str(&format!(
"&tls.allowinsecure={}",
if scv.unwrap_or(false) {
"true"
} else {
"false"
}
));
}
if !tfo.is_undef() {
_proxy_str.push_str(&format!(
"&sockopt.tcpfastopen={}",
if tfo.unwrap_or(false) {
"true"
} else {
"false"
}
));
}
}
ProxyType::Socks5 => {
_proxy_str = format!(
"{}, builtin, socks, address={}, port={}, user={}, pass={}",
node.remark, hostname, port, username, password
);
}
ProxyType::HTTP => {
_proxy_str = format!(
"{}, builtin, http, address={}, port={}, user={}, pass={}",
node.remark, hostname, port, username, password
);
}
_ => continue,
}
ini.set("{NONAME}", &_proxy_str, "").unwrap_or(());
remarks_list.push(node.remark.clone());
nodelist.push(node.clone());
}
ini.set_current_section("EndpointGroup");
for group in extra_proxy_group {
match group.group_type {
ProxyGroupType::Select
| ProxyGroupType::URLTest
| ProxyGroupType::Fallback
| ProxyGroupType::LoadBalance => {
let mut filtered_nodelist = Vec::new();
for proxy_name in &group.proxies {
group_generate(proxy_name, &nodelist, &mut filtered_nodelist, false, ext);
}
if filtered_nodelist.is_empty() {
if remarks_list.is_empty() {
filtered_nodelist.push("DIRECT".to_string());
} else {
filtered_nodelist = remarks_list.clone();
}
}
let proxy_str = format!(
"{}, {}, latency, interval=300, timeout=6",
group.name,
join(&filtered_nodelist, ":")
);
ini.set("{NONAME}", &proxy_str, "").unwrap_or(());
}
_ => continue,
}
}
if ext.enable_rule_generator {
ruleset_to_surge(
ini,
ruleset_content_array,
0,
ext.overwrite_original_rules,
"",
)
.await;
}
}