From fc47e4d5845fb20366ef827d8459186ffaa13dbc Mon Sep 17 00:00:00 2001 From: Max Vorobev Date: Fri, 13 Feb 2026 00:42:06 +0300 Subject: [PATCH] feature: support show_links = "*" --- src/config/mod.rs | 97 ++++++++++++++++++++++++++++++++++++++++++++++- src/main.rs | 2 +- 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/src/config/mod.rs b/src/config/mod.rs index 14ac6ab..887973e 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -316,6 +316,101 @@ pub struct ListenerConfig { pub announce_ip: Option, } +// ============= ShowLink ============= + +/// Controls which users' proxy links are displayed at startup. +/// +/// In TOML, this can be: +/// - `show_link = "*"` — show links for all users +/// - `show_link = ["a", "b"]` — show links for specific users +/// - omitted — show no links (default) +#[derive(Debug, Clone)] +pub enum ShowLink { + /// Don't show any links (default when omitted) + None, + /// Show links for all configured users + All, + /// Show links for specific users + Specific(Vec), +} + +impl Default for ShowLink { + fn default() -> Self { + ShowLink::None + } +} + +impl ShowLink { + /// Returns true if no links should be shown + pub fn is_empty(&self) -> bool { + matches!(self, ShowLink::None) || matches!(self, ShowLink::Specific(v) if v.is_empty()) + } + + /// Resolve the list of user names to display, given all configured users + pub fn resolve_users<'a>(&'a self, all_users: &'a HashMap) -> Vec<&'a String> { + match self { + ShowLink::None => vec![], + ShowLink::All => { + let mut names: Vec<&String> = all_users.keys().collect(); + names.sort(); + names + } + ShowLink::Specific(names) => names.iter().collect(), + } + } +} + +impl Serialize for ShowLink { + fn serialize(&self, serializer: S) -> std::result::Result { + match self { + ShowLink::None => Vec::::new().serialize(serializer), + ShowLink::All => serializer.serialize_str("*"), + ShowLink::Specific(v) => v.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for ShowLink { + fn deserialize>(deserializer: D) -> std::result::Result { + use serde::de; + + struct ShowLinkVisitor; + + impl<'de> de::Visitor<'de> for ShowLinkVisitor { + type Value = ShowLink; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str(r#""*" or an array of user names"#) + } + + fn visit_str(self, v: &str) -> std::result::Result { + if v == "*" { + Ok(ShowLink::All) + } else { + Err(de::Error::invalid_value( + de::Unexpected::Str(v), + &r#""*""#, + )) + } + } + + fn visit_seq>(self, mut seq: A) -> std::result::Result { + let mut names = Vec::new(); + while let Some(name) = seq.next_element::()? { + names.push(name); + } + if names.is_empty() { + Ok(ShowLink::None) + } else { + Ok(ShowLink::Specific(names)) + } + } + } + + deserializer.deserialize_any(ShowLinkVisitor) + } +} + // ============= Main Config ============= #[derive(Debug, Clone, Serialize, Deserialize, Default)] @@ -339,7 +434,7 @@ pub struct ProxyConfig { pub upstreams: Vec, #[serde(default)] - pub show_link: Vec, + pub show_link: ShowLink, /// DC address overrides for non-standard DCs (CDN, media, test, etc.) /// Keys are DC indices as strings, values are "ip:port" addresses. diff --git a/src/main.rs b/src/main.rs index b28167c..280ce25 100644 --- a/src/main.rs +++ b/src/main.rs @@ -223,7 +223,7 @@ async fn main() -> Result<(), Box> { if !config.show_link.is_empty() { info!("--- Proxy Links ({}) ---", public_ip); - for user_name in &config.show_link { + for user_name in config.show_link.resolve_users(&config.access.users) { if let Some(secret) = config.access.users.get(user_name) { info!("User: {}", user_name); if config.general.modes.classic {