mod http;
use tracing::warn;
use crate::outbound::OutboundConnector;
use http::https_get;
const DOMAINS_URL_HOST: &str = "raw.githubusercontent.com";
const DOMAINS_URL_PATH: &str = "/Flowseal/tg-ws-proxy/refs/heads/main/.github/cfproxy-domains.txt";
const REAL_SUFFIX: &str = ".co.uk";
static FALLBACK_ENCODED: &[&str] = &[
"virkgj.com",
"vmmzovy.com",
"mkuosckvso.com",
"zaewayzmplad.com",
"twdmbzcm.com",
];
pub fn deobfuscate(s: &str) -> Option<String> {
let prefix = s.strip_suffix(".com")?;
let n = prefix.chars().filter(|c| c.is_ascii_alphabetic()).count() as i32;
let decoded: String = prefix
.chars()
.map(|c| {
if c.is_ascii_lowercase() {
let v = ((c as i32 - b'a' as i32) - n).rem_euclid(26) as u8 + b'a';
v as char
} else if c.is_ascii_uppercase() {
let v = ((c as i32 - b'A' as i32) - n).rem_euclid(26) as u8 + b'A';
v as char
} else {
c
}
})
.collect();
Some(format!("{}{}", decoded, REAL_SUFFIX))
}
fn parse_domain_list(text: &str) -> Vec<String> {
text.lines()
.map(str::trim)
.filter(|l| !l.is_empty() && !l.starts_with('#'))
.filter_map(deobfuscate)
.collect()
}
fn fallback_domains() -> Vec<String> {
FALLBACK_ENCODED
.iter()
.filter_map(|s| deobfuscate(s))
.collect()
}
pub async fn fetch_default_domains() -> Vec<String> {
let outbound = OutboundConnector::direct();
fetch_default_domains_with_outbound(&outbound).await
}
pub async fn fetch_default_domains_with_outbound(outbound: &OutboundConnector) -> Vec<String> {
match https_get(DOMAINS_URL_HOST, DOMAINS_URL_PATH, outbound).await {
Ok(body) => {
let domains = parse_domain_list(&body);
if domains.is_empty() {
warn!("Default domain list from GitHub was empty; using built-in fallback");
fallback_domains()
} else {
domains
}
}
Err(e) => {
warn!(
"Failed to fetch default CF domain list ({}); using built-in fallback",
e
);
fallback_domains()
}
}
}
#[cfg(test)]
mod tests;