use crate::models::{ExtraSettings, Proxy, ProxyType, SSR_CIPHERS, SS_CIPHERS};
use crate::utils::base64::{base64_encode, url_safe_base64_encode};
use crate::utils::url::url_encode;
use log::error;
use bitflags::bitflags;
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ProxyUriTypes: u32 {
const SS = 0b0001;
const SSR = 0b0010;
const VMESS = 0b0100;
const TROJAN = 0b1000;
const MIXED = Self::SS.bits() | Self::SSR.bits() | Self::VMESS.bits() | Self::TROJAN.bits();
}
}
fn vmess_link_construct(
remark: &str,
hostname: &str,
port: u16,
fake_type: Option<&str>,
user_id: &str,
alter_id: u16,
transfer_protocol: &str,
path: &str,
host: &str,
tls: &str,
) -> String {
let mut json = serde_json::json!({
"v": "2",
"ps": remark,
"add": hostname,
"port": port.to_string(),
"id": user_id,
"aid": alter_id.to_string(),
"net": transfer_protocol,
"path": path,
"host": host,
"tls": tls
});
if let Some(ft) = fake_type {
json["type"] = serde_json::Value::String(ft.to_string());
}
match serde_json::to_string(&json) {
Ok(result) => result,
Err(e) => {
error!("Failed to serialize VMess JSON: {}", e);
String::new()
}
}
}
pub fn proxy_to_single(
nodes: &mut Vec<Proxy>,
types: ProxyUriTypes,
ext: &mut ExtraSettings,
) -> String {
let mut all_links = String::new();
for node in nodes {
let remark = &node.remark;
let hostname = &node.hostname;
let port = node.port.to_string();
let password = node.password.as_deref().unwrap_or("");
let method = node.encrypt_method.as_deref().unwrap_or("");
let plugin = node.plugin.as_deref().unwrap_or("");
let plugin_opts = node.plugin_option.as_deref().unwrap_or("");
let protocol = node.protocol.as_deref().unwrap_or("");
let protocol_param = node.protocol_param.as_deref().unwrap_or("");
let obfs = node.obfs.as_deref().unwrap_or("");
let obfs_param = node.obfs_param.as_deref().unwrap_or("");
let user_id = node.user_id.as_deref().unwrap_or("");
let transfer_protocol = node.transfer_protocol.as_deref().unwrap_or("");
let host = node.host.as_deref().unwrap_or("");
let path = node.path.as_deref().unwrap_or("");
let fake_type = node.fake_type.as_deref();
let tls_secure = node.tls_secure;
let alter_id = node.alter_id;
let group = node.group.as_ref();
let mut _proxy_str = String::new();
match node.proxy_type {
ProxyType::Shadowsocks => {
if types.contains(ProxyUriTypes::SS) {
_proxy_str = format!(
"ss://{}@{}:{}",
url_safe_base64_encode(&format!("{}:{}", method, password)),
hostname,
port
);
if !plugin.is_empty() && !plugin_opts.is_empty() {
_proxy_str.push_str(&format!(
"/?plugin={}",
url_encode(&format!("{};{}", plugin, plugin_opts))
));
}
_proxy_str.push_str(&format!("#{}", url_encode(remark)));
} else if types.contains(ProxyUriTypes::SSR) {
if SSR_CIPHERS.contains(&method) && plugin.is_empty() {
_proxy_str = format!(
"ssr://{}",
url_safe_base64_encode(&format!(
"{}:{}:origin:{}:plain:{}/?group={}&remarks={}",
hostname,
port,
method,
url_safe_base64_encode(password),
url_safe_base64_encode(group),
url_safe_base64_encode(remark)
))
);
} else {
continue;
}
} else {
continue;
}
}
ProxyType::ShadowsocksR => {
if types.contains(ProxyUriTypes::SSR) {
_proxy_str = format!(
"ssr://{}",
url_safe_base64_encode(&format!(
"{}:{}:{}:{}:{}:{}/?group={}&remarks={}&obfsparam={}&protoparam={}",
hostname,
port,
protocol,
method,
obfs,
url_safe_base64_encode(password),
url_safe_base64_encode(group),
url_safe_base64_encode(remark),
url_safe_base64_encode(obfs_param),
url_safe_base64_encode(protocol_param)
))
);
} else if types.contains(ProxyUriTypes::SS) {
if SS_CIPHERS.contains(&method) && protocol == "origin" && obfs == "plain" {
_proxy_str = format!(
"ss://{}@{}:{}#{}",
url_safe_base64_encode(&format!("{}:{}", method, password)),
hostname,
port,
url_encode(remark)
);
} else {
continue;
}
} else {
continue;
}
}
ProxyType::VMess => {
if !types.contains(ProxyUriTypes::VMESS) {
continue;
}
let vmess_json = vmess_link_construct(
remark,
hostname,
node.port,
fake_type,
user_id,
alter_id,
transfer_protocol,
path,
host,
if tls_secure { "tls" } else { "" },
);
_proxy_str = format!("vmess://{}", base64_encode(&vmess_json));
}
ProxyType::Trojan => {
if !types.contains(ProxyUriTypes::TROJAN) {
continue;
}
_proxy_str = format!(
"trojan://{}@{}:{}?allowInsecure={}",
password,
hostname,
port,
if node.allow_insecure.unwrap_or(false) {
"1"
} else {
"0"
}
);
if !host.is_empty() {
_proxy_str.push_str(&format!("&sni={}", host));
}
if transfer_protocol == "ws" {
_proxy_str.push_str("&ws=1");
if !path.is_empty() {
_proxy_str.push_str(&format!("&wspath={}", url_encode(path)));
}
}
_proxy_str.push_str(&format!("#{}", url_encode(remark)));
}
_ => continue,
}
all_links.push_str(&_proxy_str);
all_links.push('\n');
}
if ext.nodelist {
all_links
} else {
base64_encode(&all_links)
}
}