alroyso 1 year ago
parent
commit
19fc5003e0

+ 120 - 0
example/lib/main.dart

@@ -5,6 +5,7 @@ import 'dart:async';
 
 import 'package:flutter/services.dart';
 import 'package:wl_base_help/wl_base_help.dart';
+import 'package:wl_base_help/wl_clash_gen.dart';
 
 void main() {
   runApp(const MyApp());
@@ -30,6 +31,117 @@ class _MyAppState extends State<MyApp> {
     initPlatformState();
   }
 
+  Future<void> initBuild() async {
+    var baseConfig = BasicConfig(
+        mixedPort: 9788,
+        allowLan: false,
+        bindAddress: '*',
+        mode: ModeType.rule.name,
+        logLevel: LogLevel.info.name,
+        externalController: '127.0.0.1:9799',
+        unifiedDelay: false,
+        geodataMode: true,
+        tcpConcurrent: true,
+        findProcessMode: 'strict',
+        globalClientFingerprint: 'chrome');
+
+    var tunConfig = TunConfig(
+        enable: false,
+        stack: 'system',
+        dnsHijack: ['198.18.0.2:53'],
+        autoRoute: true,
+        autoDetectInterface: true);
+    var realityConfig = Reality(
+        publicKey: 'dakfjlkdsjflsajlsdfa', shortID: 'sajdsaljlaslallasla');
+
+    var wsOpts = WsOpts(
+        path: '/path',
+        headers: {'Host': 'v2ray.com'}
+    );
+
+    List<ProxyConfig> proxyConfg = [
+      ProxyConfig(
+          name: "测试",
+          type: "ss",
+          server: "1.1.1.1",
+          port: 20202,
+          password: 'fasla',
+          udp: true,
+          cipher: "aes-256-gmc"),
+      ProxyConfig(
+          name: "测试trojan",
+          type: "trojan",
+          server: "1.1.1.1",
+          port: 20202,
+          password: 'fadsdaa',
+          udp: true,
+          tls: true),
+      ProxyConfig(
+          name: "测试vless",
+          type: "vless",
+          server: "1.1.1.1",
+          port: 20202,
+          udp: true,
+          tls: true,
+          flow: 'xtls-rprx-vision',
+          servername: 'www.yahoo.com',
+          realityOpts: realityConfig,
+          uuid: '001001001-0101010101-191919191919',
+          clientFingerprint: 'chrome'),
+      ProxyConfig(
+          name: "测试vmess",
+          type: "vmess",
+          server: "1.1.1.1",
+          port: 20202,
+          udp: true,
+          tls: true,
+          uuid: '001001001-0101010101-191919191919',
+          cipher: 'auto',
+          alterId: 0,
+          wsOpts: wsOpts,
+      ),
+    ];
+    List<ProxyGroup> proxyGroups = [
+      ProxyGroup(
+          name: "proxy",
+          type: "select",
+          proxies: proxyConfg.map((e) => e.name).toList())
+    ];
+    var acitonProxy = proxyGroups.map((e) => e.name).first;
+
+     List<Rule> rulesConfig = [
+      Rule(type: 'GEOSITE', condition: 'geolocation-!cn', action: acitonProxy),
+      Rule(type: 'GEOSITE', condition: 'cn', action: 'DIRECT'),
+      Rule(type: 'MATCH', condition: null, action: acitonProxy),
+     ];
+
+    var defaultDns = ['119.29.29.29','223.5.5.5', '114.114.114.114'];
+    var encDns =  ['tls://119.29.29.29', 'tls://223.5.5.5'];
+    var proxyDns = ['8.8.8.8','tls://8.8.4.4#$acitonProxy', 'https://1.0.0.1/dns-query#$acitonProxy'];
+    var dnsConfig = DNSConfig(
+        enable: true,
+        ipv6: false,
+        enhancedMode: EnhancedMode.redirHost.name,
+        defaultNameserver: defaultDns,
+        nameserver: proxyDns,
+        proxyServerNameserver: defaultDns,
+        nameserverPolicy: {
+          'www.baidu.com': defaultDns.first,
+          'geosite:cn': defaultDns
+        },
+        listen: ':1035');
+    var appConfig = AppConfigBuilder()
+        .setBasicConfig(baseConfig)
+        .setDNSConfig(dnsConfig)
+        .setTunConfig(tunConfig)
+        .setProxies(proxyConfg)
+        .setProxyGroups(proxyGroups)
+        .setRules(rulesConfig)
+        .build();
+
+    print("\n${appConfig.toYaml()}");
+  }
+
   // Platform messages are asynchronous, so we initialize in an async method.
   Future<void> initPlatformState() async {
     String platformVersion;
@@ -151,6 +263,14 @@ class _MyAppState extends State<MyApp> {
                   onPressed: () async{
                     await onkillProcess('IDMan.exe');
                   }, child: Text('隐藏控制台')),
+
+              const SizedBox(
+                height: 20,
+              ),
+              ElevatedButton(
+                  onPressed: () async{
+                    await initBuild();
+                  }, child: Text('构建clash config')),
             ],
           ),
         ),

+ 47 - 15
lib/modes/clash/app.dart

@@ -1,26 +1,58 @@
-import 'bash.dart';
-import 'dns.dart';
-import 'groups.dart';
-import 'proxies.dart';
-import 'rules.dart';
-import 'tun.dart';
+import 'config_imports.dart';
 
 class AppConfig {
   final BasicConfig basicConfig;
-  final DNSConfig dnsConfig;
-  final TunConfig tunConfig;
-  final List<ProxyConfig> proxies;
-  final List<ProxyGroup> proxyGroups;
+  final DNSConfig? dnsConfig;
+  final TunConfig? tunConfig;
+  final List<ProxyConfig>? proxies;
+  final List<ProxyGroup>? proxyGroups;
   final List<Rule> rules;
 
   AppConfig({
     required this.basicConfig,
-    required this.dnsConfig,
-    required this.proxies,
-    required this.proxyGroups,
+    this.dnsConfig,
+    this.tunConfig,
+    this.proxies,
+    this.proxyGroups,
     required this.rules,
-    required this.tunConfig,
   });
 
-// 解析整个配置文件的方法
+  factory AppConfig.fromMap(Map<String, dynamic> map) {
+    return AppConfig(
+      basicConfig: BasicConfig.fromMap(map['basic']),
+      dnsConfig: map['dns'] != null ? DNSConfig.fromMap(map['dns']) : null,
+      tunConfig: map['tun'] != null ? TunConfig.fromMap(map['tun']) : null,
+      proxies: map['proxies'] != null ? List<ProxyConfig>.from(map['proxies'].map((x) => ProxyConfig.fromMap(x))) : null,
+      proxyGroups: map['proxy-groups'] != null ? List<ProxyGroup>.from(map['proxy-groups'].map((x) => ProxyGroup.fromMap(x))) : null,
+      rules: List<Rule>.from(map['rules'].map((x) => Rule.fromMap(x))),
+    );
+  }
+
+  String toYaml() {
+    var yaml = basicConfig.toYaml();
+    if (dnsConfig != null) {
+      yaml += '\n${dnsConfig!.toYaml()}';
+    }
+    if (tunConfig != null) {
+      yaml += '\n${tunConfig!.toYaml()}';
+    }
+
+    // 总是包含 'proxies:' 键,即使列表为空
+    var proxiesYaml = proxies != null && proxies!.isNotEmpty
+        ? proxies!.map((proxy) => proxy.toYaml()).join('\n')
+        : '';
+    yaml += '\nproxies:\n$proxiesYaml';
+
+    // 总是包含 'proxy-groups:' 键,即使列表为空
+    var proxyGroupsYaml = proxyGroups != null && proxyGroups!.isNotEmpty
+        ? proxyGroups!.map((group) => group.toYaml()).join('\n')
+        : '';
+    yaml += '\nproxy-groups:\n$proxyGroupsYaml';
+
+    var rulesYaml = rules.map((rule) => rule.toYaml()).join('\n');
+    yaml += '\nrules:\n$rulesYaml';
+
+    return yaml;
+  }
+
 }

+ 52 - 0
lib/modes/clash/app_config_builder.dart

@@ -0,0 +1,52 @@
+import 'config_imports.dart';
+
+class AppConfigBuilder {
+  BasicConfig? _basicConfig;
+  DNSConfig? _dnsConfig;
+  TunConfig? _tunConfig;
+  List<ProxyConfig>? _proxies;
+  List<ProxyGroup>? _proxyGroups;
+  List<Rule>? _rules;
+
+  AppConfigBuilder setBasicConfig(BasicConfig basicConfig) {
+    _basicConfig = basicConfig;
+    return this;
+  }
+
+  AppConfigBuilder setDNSConfig(DNSConfig? dnsConfig) {
+    _dnsConfig = dnsConfig;
+    return this;
+  }
+
+  AppConfigBuilder setTunConfig(TunConfig? tunConfig) {
+    _tunConfig = tunConfig;
+    return this;
+  }
+
+  AppConfigBuilder setProxies(List<ProxyConfig>? proxies) {
+    _proxies = proxies;
+    return this;
+  }
+
+  AppConfigBuilder setProxyGroups(List<ProxyGroup>? proxyGroups) {
+    _proxyGroups = proxyGroups;
+    return this;
+  }
+
+  AppConfigBuilder setRules(List<Rule>? rules) {
+    _rules = rules;
+    return this;
+  }
+
+  AppConfig build() {
+    // 默认的值或者抛出异常,取决于您的需求
+    return AppConfig(
+      basicConfig: _basicConfig ?? BasicConfig(mixedPort: 9788,allowLan: false,bindAddress: '*',mode: ModeType.rule.name,logLevel: LogLevel.warning.name,externalController: '127.0.0.1:9799',unifiedDelay: false,geodataMode: true,tcpConcurrent: true,findProcessMode: ProcessMode.strict.name,globalClientFingerprint: Fingerprint.chrome.name),
+      dnsConfig: _dnsConfig,
+      tunConfig: _tunConfig,
+      proxies: _proxies ?? [],
+      proxyGroups: _proxyGroups ?? [],
+      rules: _rules ?? [],
+    );
+  }
+}

+ 30 - 0
lib/modes/clash/bash.dart

@@ -1,3 +1,33 @@
+//GLOBAL
+enum ModeType {
+  rule,
+  global,
+  direct,
+}
+enum LogLevel {
+  silent,
+  error,
+  warning,
+  info,
+  debug,
+}
+
+enum ProcessMode {
+  always,
+  strict,
+  off
+}
+
+enum Fingerprint{
+  chrome,
+  firefox,
+  safari,
+  iOS,
+  android,
+  edge,
+  random
+}
+
 class BasicConfig {
   final int mixedPort;
   final bool allowLan;

+ 8 - 0
lib/modes/clash/config_imports.dart

@@ -0,0 +1,8 @@
+// config_imports.dart
+export 'app.dart';
+export 'bash.dart';
+export 'dns.dart';
+export 'groups.dart';
+export 'proxies.dart';
+export 'rules.dart';
+export 'tun.dart';

+ 118 - 18
lib/modes/clash/dns.dart

@@ -1,3 +1,31 @@
+//fake-ip / redir-host
+enum EnhancedMode {
+  fakeIp,
+  redirHost;
+
+  String get name {
+    switch (this) {
+      case EnhancedMode.fakeIp:
+        return 'fake-ip';
+      case EnhancedMode.redirHost:
+        return 'redir-host';
+      default:
+        return '';
+    }
+  }
+
+  String get value {
+    switch (this) {
+      case EnhancedMode.fakeIp:
+        return 'fake-ip';
+      case EnhancedMode.redirHost:
+        return 'redir-host';
+      default:
+        return '';
+    }
+  }
+}
+
 class DNSConfig {
   final bool enable;
   final String listen;
@@ -6,8 +34,11 @@ class DNSConfig {
   final List<String> defaultNameserver;
   final List<String> nameserver;
   final List<String> proxyServerNameserver;
-  final Map<String, List<String>> nameserverPolicy;
-
+  final Map<String,dynamic> nameserverPolicy;
+  final String? fakeIpRange;
+  final List<String>? fakeIpFilter;
+  final List<String>? fallback;
+  final FallbackFilter? fallbackFilter;
   DNSConfig({
     required this.enable,
     required this.listen,
@@ -17,9 +48,12 @@ class DNSConfig {
     required this.nameserver,
     required this.proxyServerNameserver,
     required this.nameserverPolicy,
+    this.fakeIpRange,
+    this.fakeIpFilter,
+    this.fallbackFilter,
+    this.fallback
   });
 
-// 同样可以添加解析方法
   factory DNSConfig.fromMap(Map<String, dynamic> map) {
     return DNSConfig(
       enable: map['enable'],
@@ -29,7 +63,11 @@ class DNSConfig {
       defaultNameserver: List<String>.from(map['default-nameserver']),
       nameserver: List<String>.from(map['nameserver']),
       proxyServerNameserver: List<String>.from(map['proxy-server-nameserver']),
-      nameserverPolicy: Map.from(map['nameserver-policy']).map((key, value) => MapEntry(key, List<String>.from(value))),
+      nameserverPolicy: Map.from(map['nameserver-policy']).map((key, value) => MapEntry(key, value)),
+      fakeIpRange: map['fake-ip-range'],
+      fakeIpFilter: map.containsKey('fake-ip-filter') ? List<String>.from(map['fake-ip-filter']) : null,
+      fallback: map.containsKey('fallback') ? List<String>.from(map['fallback']) : null,
+      fallbackFilter: map.containsKey('fallback-filter') ? FallbackFilter() : null,
     );
   }
 
@@ -43,33 +81,95 @@ class DNSConfig {
       'nameserver': nameserver,
       'proxy-server-nameserver': proxyServerNameserver,
       'nameserver-policy': nameserverPolicy.map((key, value) => MapEntry(key, value)),
+      if (fakeIpRange != null) 'fake-ip-range': fakeIpRange,
+      if (fakeIpFilter != null) 'fake-ip-filter': fakeIpFilter,
+      if (fallback != null) 'fallback': fallback,
+      if (fallbackFilter != null) 'fallback-filter': fallbackFilter,
     };
   }
 
   String toYaml() {
-    return '''
+    var yaml = '''
 dns:
   enable: $enable
-  listen: '$listen'
+  listen: $listen
   ipv6: $ipv6
-  enhanced-mode: $enhancedMode
-  default-nameserver:
-    ${defaultNameserver.map((ns) => '    - $ns').join('\n')}
-  nameserver:
-    ${nameserver.map((ns) => '    - $ns').join('\n')}
-  proxy-server-nameserver:
-    ${proxyServerNameserver.map((ns) => '    - $ns').join('\n')}
-  nameserver-policy:
-    ${_mapToYaml(nameserverPolicy)}
-''';
+  enhanced-mode: $enhancedMode''';
+
+    if (fakeIpRange != null) {
+      yaml += '\n  fake-ip-range: $fakeIpRange';
+    }
+    if (fakeIpFilter != null && fakeIpFilter!.isNotEmpty) {
+      yaml += '\n  fake-ip-filter:';
+      for (var filter in fakeIpFilter!) {
+        yaml += '\n    - $filter';
+      }
+    }
+
+
+   final fallback = this.fallback;
+   if(fallback != null){
+     yaml += '\n  fallback:\n    - ${fallback.map((ns) => ns).join('\n    - ')}';
+   }
+
+    yaml += '\n  default-nameserver:\n    - ${defaultNameserver.map((ns) => ns).join('\n    - ')}';
+    yaml += '\n  nameserver:\n    - ${_formatYamlList(nameserver)}';
+    yaml += '\n  proxy-server-nameserver:\n    - ${proxyServerNameserver.map((ns) => ns).join('\n    - ')}';
+    if (fallbackFilter != null) {
+      yaml += '\n${fallbackFilter!.toYaml()}';
+    }
+    yaml += '\n  nameserver-policy:\n${_mapToYaml(nameserverPolicy)}';
+
+    return yaml;
+  }
+  String _formatYamlList(List<String> list) {
+    return list.map((item) => item.contains('#') ? "'$item'" : item).join('\n    - ');
   }
 
-  String _mapToYaml(Map<String, List<String>> map) {
+  String _mapToYaml(Map<String, dynamic> map) {
     var yamlString = '';
     map.forEach((key, value) {
-      yamlString += '    "$key":\n      ${value.map((v) => '- $v').join('\n      ')}\n';
+      if (value is String) {
+        yamlString += "    '$key': '$value'\n";
+      } else if (value is List<String>) {
+        var formattedValues = value.map((v) => "'$v'").join(', ');
+        yamlString += "    '$key': [$formattedValues]\n";
+      }
     });
     return yamlString;
   }
 
 }
+
+
+class FallbackFilter {
+  final bool? geoip;
+  final String? geoipCode;
+  final List<String>? geosite;
+  final List<String>? ipcidr;
+  final List<String>? domain;
+
+  FallbackFilter({
+    this.geoip,
+    this.geoipCode,
+    this.geosite,
+    this.ipcidr,
+    this.domain,
+  });
+
+  String toYaml() {
+    var yaml = 'fallback-filter:\n';
+    if (geoip != null) yaml += '  geoip: $geoip\n';
+    if (geoipCode != null) yaml += "  geoip-code: '$geoipCode'\n";
+    if (geosite != null && geosite!.isNotEmpty) {
+      yaml += '  geosite:\n    - ${geosite!.join('\n    - ')}\n';
+    }
+    if (ipcidr != null && ipcidr!.isNotEmpty) {
+      yaml += '  ipcidr:\n    - ${ipcidr!.join('\n    - ')}\n';
+    }
+    if (domain != null && domain!.isNotEmpty) {
+      yaml += '  domain:\n    - ${domain!.map((d) => "'$d'").join('\n    - ')}\n';
+    }
+    return yaml;
+  }
+}

+ 2 - 1
lib/modes/clash/groups.dart

@@ -26,7 +26,7 @@ class ProxyGroup {
   }
 
   String toYaml() {
-    var proxiesYaml = proxies.map((p) => '    - $p').join('\n');
+    var proxiesYaml = proxies.map((p) => '      - $p').join('\n');
     return '''
   - name: $name
     type: $type
@@ -34,4 +34,5 @@ class ProxyGroup {
 $proxiesYaml
 ''';
   }
+
 }

+ 139 - 25
lib/modes/clash/proxies.dart

@@ -3,8 +3,8 @@ class ProxyConfig {
   final String type;
   final String server;
   final int port;
-  final String password;
-  final bool udp;
+
+  final bool? udp;
   // vless
   final String? flow;
   final String? servername;
@@ -15,20 +15,29 @@ class ProxyConfig {
   final int? alterId;
   // Shadowsocks特定属性
   final String? cipher;
+  final String? password;
+  final String? clientFingerprint;
+  final String? network;
+  final GrpcOpts? grpcOpts;
+  final WsOpts? wsOpts;
   ProxyConfig({
     required this.name,
     required this.type,
     required this.server,
     required this.port,
-    required this.password,
-    required this.udp,
+    this.password,
+    this.udp,
     this.flow,
     this.servername,
     this.tls,
     this.realityOpts, // 新增字段
+    this.grpcOpts,
+    this.wsOpts,
     this.uuid,
     this.alterId,
-    this.cipher
+    this.cipher,
+    this.clientFingerprint,
+    this.network
   });
 
   // 更新解析方法
@@ -44,9 +53,13 @@ class ProxyConfig {
       servername: map['servername'],
       tls: map['tls'],
       realityOpts: map['reality-opts'] != null ? Reality.fromMap(map['reality-opts']) : null,
+      grpcOpts: map['grpc-opts'] != null ? GrpcOpts.fromMap(map['grpc-opts']) : null,
+      wsOpts: map['ws-opts'] != null ? WsOpts.fromMap(map['ws-opts']) : null,
       uuid: map['uuid'],
       alterId: map['alterId'],
-      cipher:map['cipher']
+      cipher:map['cipher'],
+      clientFingerprint:map['client-fingerprint'],
+      network:map['network']
     );
   }
 
@@ -65,46 +78,147 @@ class ProxyConfig {
       'reality-opts': realityOpts?.toJson(),
       'uuid': uuid,
       'alterId': alterId,
-      'cipher' : cipher
+      'cipher' : cipher,
+      'client-fingerprint': clientFingerprint,
+      'grpc-opts': grpcOpts,
+      'ws-opts': wsOpts,
+      'network' : network,
     };
   }
 
   String toYaml() {
-    return '''
-- name: $name
-  type: $type
-  server: $server
-  port: $port
-  password: $password
-  udp: $udp
-  flow: $flow
-  servername: $servername
-  tls: $tls ?? ''
-  ${realityOpts?.toYaml() ?? ''}
-  uuid: $uuid ?? ''
-  alterId: $alterId ?? ''
-  cipher : $cipher ?? ''
-''';
+    var yaml = '''
+  - name: $name
+    type: $type
+    server: $server
+    port: $port''';
+
+
+    if (password != null) yaml += '\n    password: $password';
+    if (flow != null) yaml += '\n    flow: $flow';
+    if (servername != null) yaml += '\n    servername: $servername';
+    if (tls != null) yaml += '\n    tls: $tls';
+    final realityOpts = this.realityOpts;
+    if (realityOpts != null) {
+      yaml += '\n    reality-opts:';
+      if (realityOpts.publicKey != null) {
+        yaml += '\n      public-key: ${realityOpts.publicKey}';
+      }
+      if (realityOpts.shortID != null) {
+        yaml += '\n      short-id: ${realityOpts.shortID}';
+      }
+    }
+
+
+
+
+    if (uuid != null) {
+      yaml += '\n    uuid: $uuid';
+    }
+    if (alterId != null) yaml += '\n    alterId: $alterId';
+    if (cipher != null) yaml += '\n    cipher: $cipher';
+    if (clientFingerprint != null) '\n    client-fingerprint: $clientFingerprint';
+    if(network != null) yaml += '\n    network: $network';
+    if(udp != null) yaml += '\n    udp: $udp';
+    if (grpcOpts != null) {
+      yaml += '\n    ${grpcOpts!.toYaml()}';
+    }
+    if (wsOpts != null) {
+      yaml += '\n    ${wsOpts!.toYaml()}';
+    }
+    return yaml;
   }
+
+
 }
 class Reality {
   final String? publicKey;
+  final String? shortID;
 
-  Reality({this.publicKey});
+  Reality({this.publicKey,this.shortID});
 
   factory Reality.fromMap(Map<String, dynamic> map) {
     return Reality(
       publicKey: map['public-key'] as String?,
+      shortID: map['short-id'] as String?,
     );
   }
 
   Map<String, dynamic> toJson() {
     return {
       'public-key': publicKey,
+      'short-id' : shortID
+    };
+  }
+
+  String toYaml() {
+    var yaml = '';
+    if (publicKey != null) {
+      yaml += '  reality-opts:\n    public-key: $publicKey\n';
+    }
+    if (shortID != null) {
+      yaml += '  short-id: $shortID\n';
+    }
+    return yaml;
+  }
+}
+
+class GrpcOpts {
+  final String? grpcServiceName;
+
+  GrpcOpts({this.grpcServiceName});
+
+  factory GrpcOpts.fromMap(Map<String, dynamic> map) {
+    return GrpcOpts(
+      grpcServiceName: map['grpc-service-name'] as String?,
+    );
+  }
+
+  Map<String, dynamic> toJson() {
+    return {
+      if (grpcServiceName != null) 'grpc-service-name': grpcServiceName,
     };
   }
 
   String toYaml() {
-    return publicKey != null ? 'reality-opts:\n  public-key: $publicKey' : '';
+    if (grpcServiceName == null) {
+      return '';
+    }
+    return '  grpc-opts:\n    grpc-service-name: "$grpcServiceName"';
+  }
+}
+
+class WsOpts {
+  final String? path;
+  final Map<String, String>? headers;
+
+  WsOpts({this.path, this.headers});
+
+  factory WsOpts.fromMap(Map<String, dynamic> map) {
+    return WsOpts(
+      path: map['path'] as String?,
+      headers: (map['headers'] as Map?)?.cast<String, String>(),
+    );
   }
-}
+
+  Map<String, dynamic> toJson() {
+    return {
+      if (path != null) 'path': path,
+      if (headers != null) 'headers': headers,
+    };
+  }
+
+  String toYaml() {
+    var yaml = 'ws-opts:';
+    if (path != null) {
+      yaml += '\n      path: $path';
+    }
+    if (headers != null && headers!.isNotEmpty) {
+      yaml += '\n      headers:';
+      headers!.forEach((key, value) {
+        yaml += '\n        $key: $value';
+      });
+    }
+    return yaml;
+  }
+}

+ 5 - 5
lib/modes/clash/rules.dart

@@ -7,12 +7,12 @@
 
 class Rule {
   final String type;
-  final String condition;
+  final String? condition;
   final String action;
 
   Rule({
     required this.type,
-    required this.condition,
+    this.condition,
     required this.action,
   });
 
@@ -33,8 +33,8 @@ class Rule {
   }
 
   String toYaml() {
-    return '''
-  - $type,$condition,$action
-''';
+    // 当 condition 不为空时,包括 condition,否则省略 condition
+    String conditionPart = condition != null ? ',$condition' : '';
+    return ' - $type$conditionPart,$action';
   }
 }

+ 1 - 1
lib/modes/clash/tun.dart

@@ -39,7 +39,7 @@ tun:
   enable: $enable
   stack: $stack
   dns-hijack:
-    ${dnsHijack.map((item) => '    - $item').join('\n')}
+   ${dnsHijack.map((item) => ' - $item').join('\n')}
   auto-route: $autoRoute
   auto-detect-interface: $autoDetectInterface
 ''';

+ 9 - 0
lib/wl_clash_gen.dart

@@ -0,0 +1,9 @@
+// lib/my_plugin.dart
+export 'modes/clash/app.dart';
+export 'modes/clash/bash.dart';
+export 'modes/clash/dns.dart';
+export 'modes/clash/groups.dart';
+export 'modes/clash/proxies.dart';
+export 'modes/clash/rules.dart';
+export 'modes/clash/tun.dart';
+export 'modes/clash/app_config_builder.dart';  // 添加这一行来导出AppConfigBuilder