#[cfg(not(test))] use crate::adapters::proxy_router::{ ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig, ProxyRouterRequest, }; use crate::models::{ ComponentId, ComponentState, ComponentStatus, Profile, ProfileItemType, Protocol, ProxyProtocol, Target, }; #[cfg(test)] use crate::proxy_router::{ ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig, ProxyRouterRequest, }; use serde::{Deserialize, Serialize}; pub const PROXIFYRE_ADAPTER_ID: &str = "proxifyre"; pub const PROXIFYRE_OUTPUT_FILE: &str = "proxifyre-app-config.json"; #[derive(Debug, Clone, PartialEq, Eq)] pub struct ProxiFyreAdapter { log_level: String, bypass_lan: bool, } impl ProxiFyreAdapter { pub fn new(log_level: impl Into, bypass_lan: bool) -> Self { Self { log_level: log_level.into(), bypass_lan, } } pub fn generate_proxifyre_config( &self, request: ProxyRouterRequest<'_>, ) -> Result { let mut proxies = Vec::new(); for profile in request.profiles.iter().filter(|profile| profile.enabled) { let target = find_target(profile, request.targets)?; ensure_target_supported(profile, target, request.components)?; let app_names = app_names_for_profile(profile); if app_names.is_empty() { return Err(ProxyRouterError::new( ProxyRouterErrorKind::EmptyProfileItems, format!( "В профиле '{}' нет приложений для маршрутизации", profile.id ), )); } proxies.push(ProxiFyreProxy { app_names, socks5_proxy_endpoint: format!("{}:{}", target.host, target.port), supported_protocols: protocols_for_profile(profile), }); } Ok(ProxiFyreConfig { log_level: self.log_level.clone(), bypass_lan: self.bypass_lan, proxies, }) } } impl Default for ProxiFyreAdapter { fn default() -> Self { Self::new("Info", true) } } impl ProxyRouterAdapter for ProxiFyreAdapter { fn id(&self) -> &'static str { PROXIFYRE_ADAPTER_ID } fn output_file_name(&self) -> &'static str { PROXIFYRE_OUTPUT_FILE } fn generate_config( &self, request: ProxyRouterRequest<'_>, ) -> Result { let config = self.generate_proxifyre_config(request)?; let enabled_profiles = config.proxies.len(); let routed_apps = config .proxies .iter() .map(|proxy| proxy.app_names.len()) .sum(); let contents = serde_json::to_string_pretty(&config).map_err(|error| { ProxyRouterError::new( ProxyRouterErrorKind::Serialization, format!("Не удалось сериализовать конфиг ProxiFyre: {error}"), ) })?; Ok(ProxyRouterGeneratedConfig { adapter_id: self.id().to_string(), output_file_name: self.output_file_name().to_string(), contents, enabled_profiles, routed_apps, }) } } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ProxiFyreConfig { #[serde(rename = "logLevel")] pub log_level: String, #[serde(rename = "bypassLan")] pub bypass_lan: bool, pub proxies: Vec, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct ProxiFyreProxy { #[serde(rename = "appNames")] pub app_names: Vec, #[serde(rename = "socks5ProxyEndpoint")] pub socks5_proxy_endpoint: String, #[serde(rename = "supportedProtocols")] pub supported_protocols: Vec, } fn find_target<'a>( profile: &Profile, targets: &'a [Target], ) -> Result<&'a Target, ProxyRouterError> { targets .iter() .find(|target| target.id == profile.target_id) .ok_or_else(|| { ProxyRouterError::new( ProxyRouterErrorKind::MissingTarget, format!( "Профиль '{}' ссылается на отсутствующую цель '{}'", profile.id, profile.target_id ), ) }) } fn ensure_target_supported( profile: &Profile, target: &Target, components: &[ComponentStatus], ) -> Result<(), ProxyRouterError> { if target.protocol != ProxyProtocol::Socks5 { return Err(ProxyRouterError::new( ProxyRouterErrorKind::UnsupportedTargetProtocol, format!( "Цель '{}' использует HTTP, но ProxiFyre требует SOCKS5", target.id ), )); } if let Some(required_component) = &target.requires_component { let Some(status) = components .iter() .find(|component| &component.id == required_component) else { return Err(ProxyRouterError::new( ProxyRouterErrorKind::MissingRequiredComponent, format!( "Цель '{}' профиля '{}' требует отсутствующий компонент '{}'", target.id, profile.id, component_id_label(required_component) ), )); }; if !component_is_running(status) { return Err(ProxyRouterError::new( ProxyRouterErrorKind::RequiredComponentNotRunning, format!( "Цель '{}' профиля '{}' требует запущенный компонент '{}'", target.id, profile.id, component_id_label(required_component) ), )); } } Ok(()) } fn component_is_running(status: &ComponentStatus) -> bool { status.installed && status.running && status.state == ComponentState::Running } fn app_names_for_profile(profile: &Profile) -> Vec { let mut names = Vec::new(); for item in &profile.items { let value = item.value.trim(); if value.is_empty() { continue; } let app_name = match item.item_type { ProfileItemType::Process | ProfileItemType::Folder | ProfileItemType::Exe => value, }; if !names.iter().any(|existing| existing == app_name) { names.push(app_name.to_string()); } } names } fn protocols_for_profile(profile: &Profile) -> Vec { let mut protocols = Vec::new(); for protocol in &profile.protocols { let value = match protocol { Protocol::Tcp => "TCP", Protocol::Udp => "UDP", }; if !protocols.iter().any(|existing| existing == value) { protocols.push(value.to_string()); } } protocols } fn component_id_label(component_id: &ComponentId) -> &'static str { match component_id { ComponentId::ControlApp => "control-app", ComponentId::Proxyfier => "proxyfier", ComponentId::Singbox => "singbox", } }