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::{
BalanceStrategy, ExtraSettings, Proxy, ProxyGroupConfigs, ProxyGroupType, ProxyType,
RulesetContent,
};
use crate::utils::ini_reader::IniReader;
use crate::utils::string::join;
use log::error;
use std::collections::HashMap;
pub async fn proxy_to_loon(
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();
let mut output_nodelist = String::new();
let mut nodelist = Vec::new();
let mut remarks_list = Vec::new();
ini.store_any_line = true;
ini.add_direct_save_section("Plugin");
if ini.parse(base_conf).is_err() && !ext.nodelist {
error!(
"Loon base loader failed with error: {}",
ini.get_last_error()
);
return String::new();
}
ini.set_current_section("Proxy");
ini.erase_section();
for node in nodes.iter_mut() {
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, false);
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 protocol = node.protocol.as_deref().unwrap_or("");
let protoparam = node.protocol_param.as_deref().unwrap_or("");
let obfs = node.obfs.as_deref().unwrap_or("");
let obfsparam = node.obfs_param.as_deref().unwrap_or("");
let plugin = node.plugin.as_deref().unwrap_or("");
let pluginopts = node.plugin_option.as_deref().unwrap_or("");
let tls_secure = node.tls_secure;
let scv = ext.skip_cert_verify;
let scv = node.allow_insecure.as_ref().map_or(scv, |val| Some(*val));
let mut proxy;
match node.proxy_type {
ProxyType::Shadowsocks => {
proxy = format!(
"Shadowsocks,{},{},{},\"{}\"",
hostname, port, method, password
);
if plugin == "simple-obfs" || plugin == "obfs-local" {
if !pluginopts.is_empty() {
let plugin_opts =
pluginopts.replace(";obfs-host=", ",").replace("obfs=", "");
proxy.push_str(&format!(",{}", plugin_opts));
}
} else if !plugin.is_empty() {
continue;
}
}
ProxyType::VMess => {
let actual_method = if method == "auto" {
"chacha20-ietf-poly1305"
} else {
method
};
proxy = format!(
"vmess,{},{},{},\"{}\",over-tls={}",
hostname,
port,
actual_method,
id,
if tls_secure { "true" } else { "false" }
);
if tls_secure {
proxy.push_str(&format!(",tls-name={}", host));
}
match transproto {
"tcp" => {
proxy.push_str(",transport=tcp");
}
"ws" => {
proxy.push_str(&format!(",transport=ws,path={},host={}", path, host));
}
_ => continue,
}
if scv.is_some() {
proxy.push_str(&format!(
",skip-cert-verify={}",
if scv.unwrap_or(false) {
"true"
} else {
"false"
}
));
}
}
ProxyType::ShadowsocksR => {
proxy = format!(
"ShadowsocksR,{},{},{},\"{}\",protocol={},protocol-param={},obfs={},obfs-param={}",
hostname, port, method, password, protocol, protoparam, obfs, obfsparam
);
}
ProxyType::HTTP => {
proxy = format!("http,{},{}", hostname, port);
if !username.is_empty() {
proxy.push_str(&format!(",{}", username));
if !password.is_empty() {
proxy.push_str(&format!(",\"{}\"", password));
}
}
}
ProxyType::HTTPS => {
proxy = format!("https,{},{}", hostname, port);
if !username.is_empty() {
proxy.push_str(&format!(",{}", username));
if !password.is_empty() {
proxy.push_str(&format!(",\"{}\"", password));
}
}
if !host.is_empty() {
proxy.push_str(&format!(",tls-name={}", host));
}
if scv.is_some() {
proxy.push_str(&format!(
",skip-cert-verify={}",
if scv.unwrap_or(false) {
"true"
} else {
"false"
}
));
}
}
ProxyType::Trojan => {
proxy = format!("trojan,{},{},\"{}\"", hostname, port, password);
if !host.is_empty() {
proxy.push_str(&format!(",tls-name={}", host));
}
if scv.is_some() {
proxy.push_str(&format!(
",skip-cert-verify={}",
if scv.unwrap_or(false) {
"true"
} else {
"false"
}
));
}
}
ProxyType::Socks5 => {
proxy = format!("socks5,{},{}", hostname, port);
if !username.is_empty() && !password.is_empty() {
proxy.push_str(&format!(",{},\"{}\"", username, password));
}
proxy.push_str(&format!(
",over-tls={}",
if tls_secure { "true" } else { "false" }
));
if tls_secure {
if !host.is_empty() {
proxy.push_str(&format!(",tls-name={}", host));
}
if scv.is_some() {
proxy.push_str(&format!(
",skip-cert-verify={}",
if scv.unwrap_or(false) {
"true"
} else {
"false"
}
));
}
}
}
ProxyType::WireGuard => {
proxy = format!(
"wireguard, interface-ip={}",
node.self_ip.as_deref().unwrap_or("")
);
if let Some(ipv6) = &node.self_ipv6 {
if !ipv6.is_empty() {
proxy.push_str(&format!(", interface-ipv6={}", ipv6));
}
}
if let Some(private_key) = &node.private_key {
proxy.push_str(&format!(", private-key={}", private_key));
}
for server in &node.dns_servers {
if server.contains('.') {
proxy.push_str(&format!(", dns={}", server));
} else if server.contains(':') {
proxy.push_str(&format!(", dnsv6={}", server));
}
}
if node.mtu > 0 {
proxy.push_str(&format!(", mtu={}", node.mtu));
}
if node.keep_alive > 0 {
proxy.push_str(&format!(", keepalive={}", node.keep_alive));
}
proxy.push_str(&format!(", peers=[{{{}}}]", generate_peer(node, true)));
}
_ => continue,
}
if ext.tfo.unwrap_or(false) {
proxy.push_str(",fast-open=true");
}
if ext.udp.unwrap_or(false) {
proxy.push_str(",udp=true");
}
if ext.nodelist {
output_nodelist.push_str(&format!("{} = {}\n", remark, proxy));
} else {
ini.set("{NONAME}", &format!("{} = {}", remark, proxy), "")
.unwrap_or(());
nodelist.push(node.clone());
remarks_list.push(remark);
}
}
if ext.nodelist {
return output_nodelist;
}
let original_groups: HashMap<String, Vec<String>> = HashMap::new();
ini.set_current_section("Proxy Group");
ini.erase_section();
for group in extra_proxy_group {
let mut filtered_nodelist = Vec::new();
let mut group_str;
match group.group_type {
ProxyGroupType::Select
| ProxyGroupType::LoadBalance
| ProxyGroupType::URLTest
| ProxyGroupType::Fallback => {
}
ProxyGroupType::SSID => {
if group.proxies.len() < 2 {
continue;
}
group_str = format!("{},default={},", group.type_str(), group.proxies[0]);
group_str.push_str(&join(&group.proxies[1..], ","));
ini.set("{NONAME}", &format!("{} = {}", group.name, group_str), "")
.unwrap_or(());
continue;
}
_ => continue,
}
for proxy_name in &group.proxies {
group_generate(proxy_name, &nodelist, &mut filtered_nodelist, true, ext);
}
if filtered_nodelist.is_empty() {
filtered_nodelist.push("DIRECT".to_string());
}
let mut img_url = String::new();
if let Some(values) = original_groups.get(&group.name) {
if !values.is_empty() {
let last_item = &values[values.len() - 1];
if last_item.contains("img-url") {
img_url = last_item.clone();
}
}
}
group_str = format!("{},", group.type_str());
group_str.push_str(&join(&filtered_nodelist, ","));
if group.group_type != ProxyGroupType::Select {
group_str.push_str(&format!(",url={},interval={}", group.url, group.interval));
if group.group_type == ProxyGroupType::LoadBalance {
let algorithm = match group.strategy {
BalanceStrategy::RoundRobin => "round-robin",
BalanceStrategy::ConsistentHashing => "pcc",
};
group_str.push_str(&format!(",algorithm={}", algorithm));
if group.timeout > 0 {
group_str.push_str(&format!(",max-timeout={}", group.timeout));
}
} else if group.group_type == ProxyGroupType::URLTest {
if group.tolerance > 0 {
group_str.push_str(&format!(",tolerance={}", group.tolerance));
}
} else if group.group_type == ProxyGroupType::Fallback {
group_str.push_str(&format!(",max-timeout={}", group.timeout));
}
}
if !img_url.is_empty() {
group_str.push_str(&format!(",{}", img_url));
}
ini.set("{NONAME}", &format!("{} = {}", group.name, group_str), "")
.unwrap_or(());
}
if ext.enable_rule_generator {
ruleset_to_loon(
&mut ini,
ruleset_content_array,
ext.overwrite_original_rules,
&ext.managed_config_prefix,
)
.await;
}
ini.to_string()
}
fn generate_peer(node: &Proxy, client_id_as_reserved: bool) -> String {
let mut peer = String::new();
if let Some(public_key) = &node.public_key {
peer.push_str(&format!("public-key={}", public_key));
}
peer.push_str(&format!(", endpoint={}:{}", node.hostname, node.port));
if !node.allowed_ips.is_empty() {
peer.push_str(&format!(", allowed-ips={}", node.allowed_ips));
}
if let Some(client_id) = &node.client_id {
if !client_id.is_empty() {
if client_id_as_reserved {
peer.push_str(&format!(", reserved={}", client_id));
} else {
peer.push_str(&format!(", client-id={}", client_id));
}
}
}
peer
}
async fn ruleset_to_loon(
ini: &mut IniReader,
ruleset_content_array: &mut Vec<RulesetContent>,
overwrite_original_rules: bool,
managed_config_prefix: &str,
) {
ruleset_to_surge(
ini,
ruleset_content_array,
-4,
overwrite_original_rules,
managed_config_prefix,
)
.await;
}