Add device HWID support to sing-box subscriptions

This commit is contained in:
2026-07-08 20:31:21 +03:00
parent 7316e932f0
commit 42b85cc8fa
12 changed files with 450 additions and 19 deletions
+59 -4
View File
@@ -5,6 +5,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
use url::Url;
const SUPPORTED_PROXY_TYPES: &[&str] = &["vless", "vmess", "trojan", "shadowsocks", "hysteria2"];
const DEFAULT_APP_NAME: &str = "ProxyWarden";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubscriptionError {
@@ -33,6 +34,41 @@ pub struct ParsedSubscription {
pub servers: Vec<SubscriptionServer>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubscriptionFetchIdentity {
pub device_hwid: Option<String>,
pub app_name: String,
pub user_agent: String,
pub device_os: String,
pub device_model: String,
}
impl SubscriptionFetchIdentity {
pub fn with_device_hwid(device_hwid: Option<&str>) -> Self {
Self {
device_hwid: device_hwid
.map(str::trim)
.filter(|value| !value.is_empty())
.map(str::to_string),
..Self::default()
}
}
}
impl Default for SubscriptionFetchIdentity {
fn default() -> Self {
let device_os = std::env::consts::OS.to_string();
Self {
device_hwid: None,
app_name: DEFAULT_APP_NAME.to_string(),
user_agent: format!("{DEFAULT_APP_NAME}/{device_os}"),
device_os,
device_model: DEFAULT_APP_NAME.to_string(),
}
}
}
pub fn parse_subscription_body(body: &str) -> Result<ParsedSubscription, SubscriptionError> {
let config = match serde_json::from_str::<Value>(body) {
Ok(value) => value,
@@ -66,6 +102,13 @@ pub fn parse_user_info(header_value: Option<&str>) -> Map<String, Value> {
}
pub fn fetch_subscription(url: &str) -> Result<SubscriptionCache, SubscriptionError> {
fetch_subscription_with_identity(url, &SubscriptionFetchIdentity::default())
}
pub fn fetch_subscription_with_identity(
url: &str,
identity: &SubscriptionFetchIdentity,
) -> Result<SubscriptionCache, SubscriptionError> {
let parsed_url =
Url::parse(url).map_err(|_| SubscriptionError::new("Invalid subscription URL"))?;
if !matches!(parsed_url.scheme(), "http" | "https") {
@@ -74,11 +117,23 @@ pub fn fetch_subscription(url: &str) -> Result<SubscriptionCache, SubscriptionEr
));
}
let response = reqwest::blocking::Client::new()
let mut request = reqwest::blocking::Client::new()
.get(parsed_url)
.header("user-agent", "singbox")
.header("x-device-os", std::env::consts::OS)
.header("x-device-model", "proxywarden")
.header("user-agent", identity.user_agent.as_str())
.header("x-app-name", identity.app_name.as_str())
.header("x-device-os", identity.device_os.as_str())
.header("x-device-model", identity.device_model.as_str());
if let Some(device_hwid) = identity
.device_hwid
.as_deref()
.map(str::trim)
.filter(|value| !value.is_empty())
{
request = request.header("x-hwid", device_hwid);
}
let response = request
.send()
.map_err(|error| SubscriptionError::new(format!("Subscription request failed: {error}")))?;