@@ -637,162 +637,6 @@ fn save_config(cfg: &Config) -> Result<PathBuf, String> {
637637 Ok ( path)
638638}
639639
640- #[ derive( serde:: Serialize ) ]
641- struct ConfigWire < ' a > {
642- mode : & ' a str ,
643- google_ip : & ' a str ,
644- front_domain : & ' a str ,
645- #[ serde( skip_serializing_if = "Option::is_none" ) ]
646- script_id : Option < ScriptIdWire < ' a > > ,
647- auth_key : & ' a str ,
648- listen_host : & ' a str ,
649- listen_port : u16 ,
650- #[ serde( skip_serializing_if = "Option::is_none" ) ]
651- socks5_port : Option < u16 > ,
652- log_level : & ' a str ,
653- verify_ssl : bool ,
654- #[ serde( skip_serializing_if = "std::collections::HashMap::is_empty" ) ]
655- hosts : & ' a std:: collections:: HashMap < String , String > ,
656- #[ serde( skip_serializing_if = "Option::is_none" ) ]
657- upstream_socks5 : Option < & ' a str > ,
658- #[ serde( skip_serializing_if = "is_zero_u8" ) ]
659- parallel_relay : u8 ,
660- #[ serde( skip_serializing_if = "Option::is_none" ) ]
661- sni_hosts : Option < Vec < & ' a str > > ,
662- #[ serde( skip_serializing_if = "is_false" ) ]
663- normalize_x_graphql : bool ,
664- #[ serde( skip_serializing_if = "is_false" ) ]
665- youtube_via_relay : bool ,
666- #[ serde( skip_serializing_if = "Vec::is_empty" ) ]
667- passthrough_hosts : & ' a Vec < String > ,
668- // IP-scan knobs. These used to be missing from the wire struct, so
669- // every Save-config silently dropped them — the user would toggle
670- // "fetch from API" on, save, reopen, and find it off again. Add
671- // them here and keep them in sync if Config ever grows more.
672- #[ serde( skip_serializing_if = "is_false" ) ]
673- fetch_ips_from_api : bool ,
674- max_ips_to_scan : usize ,
675- scan_batch_size : usize ,
676- google_ip_validation : bool ,
677- /// Default false (= bypass DoH). Only emitted when explicitly true
678- /// so unchanged configs stay clean.
679- #[ serde( skip_serializing_if = "is_false" ) ]
680- tunnel_doh : bool ,
681- #[ serde( skip_serializing_if = "Vec::is_empty" ) ]
682- bypass_doh_hosts : & ' a Vec < String > ,
683- /// PR #763: default true (= browser DoH rejected, system DNS used).
684- /// Skip when matching default to keep unchanged configs clean —
685- /// emit only when the user has explicitly disabled the block.
686- #[ serde( skip_serializing_if = "is_true" ) ]
687- block_doh : bool ,
688- /// Default false. Emit only when the user enables STUN/TURN blocking.
689- #[ serde( skip_serializing_if = "is_false" ) ]
690- block_stun : bool ,
691- #[ serde( skip_serializing_if = "Vec::is_empty" ) ]
692- fronting_groups : & ' a Vec < FrontingGroup > ,
693- /// Auto-blacklist tuning + batch timeout (#391, #444, #430). Skip
694- /// serialization when matching the historical defaults so unchanged
695- /// configs stay clean — only emitted when the user has explicitly
696- /// tuned them.
697- #[ serde( skip_serializing_if = "is_default_strikes" ) ]
698- auto_blacklist_strikes : u32 ,
699- #[ serde( skip_serializing_if = "is_default_window_secs" ) ]
700- auto_blacklist_window_secs : u64 ,
701- #[ serde( skip_serializing_if = "is_default_cooldown_secs" ) ]
702- auto_blacklist_cooldown_secs : u64 ,
703- #[ serde( skip_serializing_if = "is_default_timeout_secs" ) ]
704- request_timeout_secs : u64 ,
705- /// HTTP/2 multiplexing kill switch. Default false (h2 active); only
706- /// emitted on save when the user has explicitly disabled h2, so
707- /// unchanged configs stay clean.
708- #[ serde( skip_serializing_if = "is_false" ) ]
709- force_http1 : bool ,
710- /// Exit-node config (CF-anti-bot bypass for chatgpt.com / claude.ai /
711- /// grok.com / x.com via exit-node second-hop relay). Skip when fully
712- /// default (disabled with no URL/PSK/hosts) so configs without
713- /// exit-node setup stay clean. Round-tripped through FormState so
714- /// Save preserves user-edited values.
715- #[ serde( skip_serializing_if = "is_default_exit_node" ) ]
716- exit_node : & ' a mhrv_rs:: config:: ExitNodeConfig ,
717- }
718-
719- fn is_default_strikes ( v : & u32 ) -> bool { * v == 3 }
720- fn is_default_window_secs ( v : & u64 ) -> bool { * v == 30 }
721- fn is_default_cooldown_secs ( v : & u64 ) -> bool { * v == 120 }
722- fn is_default_timeout_secs ( v : & u64 ) -> bool { * v == 30 }
723- fn is_default_exit_node ( en : & & mhrv_rs:: config:: ExitNodeConfig ) -> bool {
724- !en. enabled
725- && en. relay_url . is_empty ( )
726- && en. psk . is_empty ( )
727- && en. hosts . is_empty ( )
728- && ( en. mode . is_empty ( ) || en. mode == "selective" )
729- }
730-
731- fn is_false ( b : & bool ) -> bool {
732- !* b
733- }
734-
735- fn is_true ( b : & bool ) -> bool {
736- * b
737- }
738-
739- fn is_zero_u8 ( v : & u8 ) -> bool {
740- * v == 0
741- }
742-
743- #[ derive( serde:: Serialize ) ]
744- #[ serde( untagged) ]
745- enum ScriptIdWire < ' a > {
746- One ( & ' a str ) ,
747- Many ( Vec < & ' a str > ) ,
748- }
749-
750- impl < ' a > From < & ' a Config > for ConfigWire < ' a > {
751- fn from ( c : & ' a Config ) -> Self {
752- let script_id = c. script_id . as_ref ( ) . map ( |s| match s {
753- ScriptId :: One ( v) => ScriptIdWire :: One ( v. as_str ( ) ) ,
754- ScriptId :: Many ( v) => ScriptIdWire :: Many ( v. iter ( ) . map ( String :: as_str) . collect ( ) ) ,
755- } ) ;
756- ConfigWire {
757- mode : c. mode . as_str ( ) ,
758- google_ip : c. google_ip . as_str ( ) ,
759- front_domain : c. front_domain . as_str ( ) ,
760- script_id,
761- auth_key : c. auth_key . as_str ( ) ,
762- listen_host : c. listen_host . as_str ( ) ,
763- listen_port : c. listen_port ,
764- socks5_port : c. socks5_port ,
765- log_level : c. log_level . as_str ( ) ,
766- verify_ssl : c. verify_ssl ,
767- hosts : & c. hosts ,
768- upstream_socks5 : c. upstream_socks5 . as_deref ( ) ,
769- parallel_relay : c. parallel_relay ,
770- sni_hosts : c
771- . sni_hosts
772- . as_ref ( )
773- . map ( |v| v. iter ( ) . map ( String :: as_str) . collect ( ) ) ,
774- normalize_x_graphql : c. normalize_x_graphql ,
775- youtube_via_relay : c. youtube_via_relay ,
776- passthrough_hosts : & c. passthrough_hosts ,
777- fetch_ips_from_api : c. fetch_ips_from_api ,
778- max_ips_to_scan : c. max_ips_to_scan ,
779- scan_batch_size : c. scan_batch_size ,
780- google_ip_validation : c. google_ip_validation ,
781- tunnel_doh : c. tunnel_doh ,
782- bypass_doh_hosts : & c. bypass_doh_hosts ,
783- block_doh : c. block_doh ,
784- block_stun : c. block_stun ,
785- fronting_groups : & c. fronting_groups ,
786- auto_blacklist_strikes : c. auto_blacklist_strikes ,
787- auto_blacklist_window_secs : c. auto_blacklist_window_secs ,
788- auto_blacklist_cooldown_secs : c. auto_blacklist_cooldown_secs ,
789- request_timeout_secs : c. request_timeout_secs ,
790- force_http1 : c. force_http1 ,
791- exit_node : & c. exit_node ,
792- }
793- }
794- }
795-
796640/// Accent color — same blue used throughout the UI for primary actions.
797641const ACCENT : egui:: Color32 = egui:: Color32 :: from_rgb ( 70 , 120 , 180 ) ;
798642const ACCENT_HOVER : egui:: Color32 = egui:: Color32 :: from_rgb ( 90 , 145 , 205 ) ;
0 commit comments