Files
MWClash/lib/models/clash_config.dart
chen08209 ed7868282a Add android separates the core process
Support core status check and force restart

Optimize proxies page and access page

Update flutter and pub dependencies

Update go version

Optimize more details
2025-09-23 15:23:58 +08:00

483 lines
14 KiB
Dart

import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'generated/clash_config.freezed.dart';
part 'generated/clash_config.g.dart';
typedef HostsMap = Map<String, String>;
const defaultClashConfig = ClashConfig();
const defaultTun = Tun();
const defaultDns = Dns();
const defaultGeoXUrl = GeoXUrl();
const defaultMixedPort = 7890;
const defaultKeepAliveInterval = 30;
const defaultBypassPrivateRouteAddress = [
'1.0.0.0/8',
'2.0.0.0/7',
'4.0.0.0/6',
'8.0.0.0/7',
'11.0.0.0/8',
'12.0.0.0/6',
'16.0.0.0/4',
'32.0.0.0/3',
'64.0.0.0/3',
'96.0.0.0/4',
'112.0.0.0/5',
'120.0.0.0/6',
'124.0.0.0/7',
'126.0.0.0/8',
'128.0.0.0/3',
'160.0.0.0/5',
'168.0.0.0/8',
'169.0.0.0/9',
'169.128.0.0/10',
'169.192.0.0/11',
'169.224.0.0/12',
'169.240.0.0/13',
'169.248.0.0/14',
'169.252.0.0/15',
'169.255.0.0/16',
'170.0.0.0/7',
'172.0.0.0/12',
'172.32.0.0/11',
'172.64.0.0/10',
'172.128.0.0/9',
'173.0.0.0/8',
'174.0.0.0/7',
'176.0.0.0/4',
'192.0.0.0/9',
'192.128.0.0/11',
'192.160.0.0/13',
'192.169.0.0/16',
'192.170.0.0/15',
'192.172.0.0/14',
'192.176.0.0/12',
'192.192.0.0/10',
'193.0.0.0/8',
'194.0.0.0/7',
'196.0.0.0/6',
'200.0.0.0/5',
'208.0.0.0/4',
'240.0.0.0/5',
'248.0.0.0/6',
'252.0.0.0/7',
'254.0.0.0/8',
'255.0.0.0/9',
'255.128.0.0/10',
'255.192.0.0/11',
'255.224.0.0/12',
'255.240.0.0/13',
'255.248.0.0/14',
'255.252.0.0/15',
'255.254.0.0/16',
'255.255.0.0/17',
'255.255.128.0/18',
'255.255.192.0/19',
'255.255.224.0/20',
'255.255.240.0/21',
'255.255.248.0/22',
'255.255.252.0/23',
'255.255.254.0/24',
'255.255.255.0/25',
'255.255.255.128/26',
'255.255.255.192/27',
'255.255.255.224/28',
'255.255.255.240/29',
'255.255.255.248/30',
'255.255.255.252/31',
'255.255.255.254/32',
'::/1',
'8000::/2',
'c000::/3',
'e000::/4',
'f000::/5',
'f800::/6',
'fe00::/9',
'fec0::/10',
];
@freezed
abstract class ProxyGroup with _$ProxyGroup {
const factory ProxyGroup({
required String name,
@JsonKey(fromJson: GroupType.parseProfileType) required GroupType type,
List<String>? proxies,
List<String>? use,
int? interval,
bool? lazy,
String? url,
int? timeout,
@JsonKey(name: 'max-failed-times') int? maxFailedTimes,
String? filter,
@JsonKey(name: 'expected-filter') String? excludeFilter,
@JsonKey(name: 'exclude-type') String? excludeType,
@JsonKey(name: 'expected-status') dynamic expectedStatus,
bool? hidden,
String? icon,
}) = _ProxyGroup;
factory ProxyGroup.fromJson(Map<String, Object?> json) =>
_$ProxyGroupFromJson(json);
}
@freezed
abstract class RuleProvider with _$RuleProvider {
const factory RuleProvider({required String name}) = _RuleProvider;
factory RuleProvider.fromJson(Map<String, Object?> json) =>
_$RuleProviderFromJson(json);
}
@freezed
abstract class Sniffer with _$Sniffer {
const factory Sniffer({
@Default(false) bool enable,
@Default(true) @JsonKey(name: 'override-destination') bool overrideDest,
@Default([]) List<String> sniffing,
@Default([]) @JsonKey(name: 'force-domain') List<String> forceDomain,
@Default([]) @JsonKey(name: 'skip-src-address') List<String> skipSrcAddress,
@Default([]) @JsonKey(name: 'skip-dst-address') List<String> skipDstAddress,
@Default([]) @JsonKey(name: 'skip-domain') List<String> skipDomain,
@Default([]) @JsonKey(name: 'port-whitelist') List<String> port,
@Default(true) @JsonKey(name: 'force-dns-mapping') bool forceDnsMapping,
@Default(true) @JsonKey(name: 'parse-pure-ip') bool parsePureIp,
@Default({}) Map<String, SnifferConfig> sniff,
}) = _Sniffer;
factory Sniffer.fromJson(Map<String, Object?> json) =>
_$SnifferFromJson(json);
}
List<String> _formJsonPorts(List? ports) {
return ports?.map((item) => item.toString()).toList() ?? [];
}
@freezed
abstract class SnifferConfig with _$SnifferConfig {
const factory SnifferConfig({
@Default([]) @JsonKey(fromJson: _formJsonPorts) List<String> ports,
@JsonKey(name: 'override-destination') bool? overrideDest,
}) = _SnifferConfig;
factory SnifferConfig.fromJson(Map<String, Object?> json) =>
_$SnifferConfigFromJson(json);
}
@freezed
abstract class Tun with _$Tun {
const factory Tun({
@Default(false) bool enable,
@Default(appName) String device,
@JsonKey(name: 'auto-route') @Default(false) bool autoRoute,
@Default(TunStack.mixed) TunStack stack,
@JsonKey(name: 'dns-hijack') @Default(['any:53']) List<String> dnsHijack,
@JsonKey(name: 'route-address') @Default([]) List<String> routeAddress,
}) = _Tun;
factory Tun.fromJson(Map<String, Object?> json) => _$TunFromJson(json);
factory Tun.safeFormJson(Map<String, Object?>? json) {
if (json == null) {
return defaultTun;
}
try {
return Tun.fromJson(json);
} catch (_) {
return defaultTun;
}
}
}
extension TunExt on Tun {
Tun getRealTun(RouteMode routeMode) {
final mRouteAddress = routeMode == RouteMode.bypassPrivate
? defaultBypassPrivateRouteAddress
: routeAddress;
return switch (system.isDesktop) {
true => copyWith(autoRoute: true, routeAddress: []),
false => copyWith(
autoRoute: mRouteAddress.isEmpty ? true : false,
routeAddress: mRouteAddress,
),
};
}
}
@freezed
abstract class FallbackFilter with _$FallbackFilter {
const factory FallbackFilter({
@Default(true) bool geoip,
@Default('CN') @JsonKey(name: 'geoip-code') String geoipCode,
@Default(['gfw']) List<String> geosite,
@Default(['240.0.0.0/4']) List<String> ipcidr,
@Default(['+.google.com', '+.facebook.com', '+.youtube.com'])
List<String> domain,
}) = _FallbackFilter;
factory FallbackFilter.fromJson(Map<String, Object?> json) =>
_$FallbackFilterFromJson(json);
}
@freezed
abstract class Dns with _$Dns {
const factory Dns({
@Default(true) bool enable,
@Default('0.0.0.0:1053') String listen,
@Default(false) @JsonKey(name: 'prefer-h3') bool preferH3,
@Default(true) @JsonKey(name: 'use-hosts') bool useHosts,
@Default(true) @JsonKey(name: 'use-system-hosts') bool useSystemHosts,
@Default(false) @JsonKey(name: 'respect-rules') bool respectRules,
@Default(false) bool ipv6,
@Default(['223.5.5.5'])
@JsonKey(name: 'default-nameserver')
List<String> defaultNameserver,
@Default(DnsMode.fakeIp)
@JsonKey(name: 'enhanced-mode')
DnsMode enhancedMode,
@Default('198.18.0.1/16')
@JsonKey(name: 'fake-ip-range')
String fakeIpRange,
@Default(['*.lan', 'localhost.ptlogin2.qq.com'])
@JsonKey(name: 'fake-ip-filter')
List<String> fakeIpFilter,
@Default({
'www.baidu.com': '114.114.114.114',
'+.internal.crop.com': '10.0.0.1',
'geosite:cn': 'https://doh.pub/dns-query',
})
@JsonKey(name: 'nameserver-policy')
Map<String, String> nameserverPolicy,
@Default(['https://doh.pub/dns-query', 'https://dns.alidns.com/dns-query'])
List<String> nameserver,
@Default(['tls://8.8.4.4', 'tls://1.1.1.1']) List<String> fallback,
@Default(['https://doh.pub/dns-query'])
@JsonKey(name: 'proxy-server-nameserver')
List<String> proxyServerNameserver,
@Default(FallbackFilter())
@JsonKey(name: 'fallback-filter')
FallbackFilter fallbackFilter,
}) = _Dns;
factory Dns.fromJson(Map<String, Object?> json) => _$DnsFromJson(json);
factory Dns.safeDnsFromJson(Map<String, Object?> json) {
try {
return Dns.fromJson(json);
} catch (_) {
return const Dns();
}
}
}
@freezed
abstract class GeoXUrl with _$GeoXUrl {
const factory GeoXUrl({
@Default(
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb',
)
String mmdb,
@Default(
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb',
)
String asn,
@Default(
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat',
)
String geoip,
@Default(
'https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat',
)
String geosite,
}) = _GeoXUrl;
factory GeoXUrl.fromJson(Map<String, Object?> json) =>
_$GeoXUrlFromJson(json);
factory GeoXUrl.safeFormJson(Map<String, Object?>? json) {
if (json == null) {
return defaultGeoXUrl;
}
try {
return GeoXUrl.fromJson(json);
} catch (_) {
return defaultGeoXUrl;
}
}
}
@freezed
abstract class ParsedRule with _$ParsedRule {
const factory ParsedRule({
required RuleAction ruleAction,
String? content,
String? ruleTarget,
String? ruleProvider,
String? subRule,
@Default(false) bool noResolve,
@Default(false) bool src,
}) = _ParsedRule;
factory ParsedRule.parseString(String value) {
final splits = value.split(',');
final shortSplits = splits
.where((item) => !item.contains('src') && !item.contains('no-resolve'))
.toList();
final ruleAction = RuleAction.values.firstWhere(
(item) => item.value == shortSplits.first,
orElse: () => RuleAction.DOMAIN,
);
String? subRule;
String? ruleTarget;
if (ruleAction == RuleAction.SUB_RULE) {
subRule = shortSplits.last;
} else {
ruleTarget = shortSplits.last;
}
String? content;
String? ruleProvider;
if (ruleAction == RuleAction.RULE_SET) {
ruleProvider = shortSplits.sublist(1, shortSplits.length - 1).join(',');
} else {
content = shortSplits.sublist(1, shortSplits.length - 1).join(',');
}
return ParsedRule(
ruleAction: ruleAction,
content: content,
src: splits.contains('src'),
ruleProvider: ruleProvider,
noResolve: splits.contains('no-resolve'),
subRule: subRule,
ruleTarget: ruleTarget,
);
}
}
extension ParsedRuleExt on ParsedRule {
String get value {
return [
ruleAction.value,
ruleAction == RuleAction.RULE_SET ? ruleProvider : content,
ruleAction == RuleAction.SUB_RULE ? subRule : ruleTarget,
if (ruleAction.hasParams) ...[
if (src) 'src',
if (noResolve) 'no-resolve',
],
].join(',');
}
}
@freezed
abstract class Rule with _$Rule {
const factory Rule({required String id, required String value}) = _Rule;
factory Rule.value(String value) {
return Rule(value: value, id: utils.uuidV4);
}
factory Rule.fromJson(Map<String, Object?> json) => _$RuleFromJson(json);
}
@freezed
abstract class SubRule with _$SubRule {
const factory SubRule({required String name}) = _SubRule;
factory SubRule.fromJson(Map<String, Object?> json) =>
_$SubRuleFromJson(json);
}
List<Rule> _genRule(List<dynamic>? rules) {
if (rules == null) {
return [];
}
return rules.map((item) => Rule.value(item)).toList();
}
List<RuleProvider> _genRuleProviders(Map<String, dynamic> json) {
return json.entries.map((entry) => RuleProvider(name: entry.key)).toList();
}
List<SubRule> _genSubRules(Map<String, dynamic> json) {
return json.entries.map((entry) => SubRule(name: entry.key)).toList();
}
@freezed
abstract class ClashConfigSnippet with _$ClashConfigSnippet {
const factory ClashConfigSnippet({
@Default([]) @JsonKey(name: 'proxy-groups') List<ProxyGroup> proxyGroups,
@JsonKey(fromJson: _genRule, name: 'rules') @Default([]) List<Rule> rule,
@JsonKey(name: 'rule-providers', fromJson: _genRuleProviders)
@Default([])
List<RuleProvider> ruleProvider,
@JsonKey(name: 'sub-rules', fromJson: _genSubRules)
@Default([])
List<SubRule> subRules,
}) = _ClashConfigSnippet;
factory ClashConfigSnippet.fromJson(Map<String, Object?> json) =>
_$ClashConfigSnippetFromJson(json);
}
@freezed
abstract class ClashConfig with _$ClashConfig {
const factory ClashConfig({
@Default(defaultMixedPort) @JsonKey(name: 'mixed-port') int mixedPort,
@Default(0) @JsonKey(name: 'socks-port') int socksPort,
@Default(0) @JsonKey(name: 'port') int port,
@Default(0) @JsonKey(name: 'redir-port') int redirPort,
@Default(0) @JsonKey(name: 'tproxy-port') int tproxyPort,
@Default(Mode.rule) Mode mode,
@Default(false) @JsonKey(name: 'allow-lan') bool allowLan,
@Default(LogLevel.error) @JsonKey(name: 'log-level') LogLevel logLevel,
@Default(false) bool ipv6,
@Default(FindProcessMode.always)
@JsonKey(
name: 'find-process-mode',
unknownEnumValue: FindProcessMode.always,
)
FindProcessMode findProcessMode,
@Default(defaultKeepAliveInterval)
@JsonKey(name: 'keep-alive-interval')
int keepAliveInterval,
@Default(true) @JsonKey(name: 'unified-delay') bool unifiedDelay,
@Default(true) @JsonKey(name: 'tcp-concurrent') bool tcpConcurrent,
@Default(defaultTun) @JsonKey(fromJson: Tun.safeFormJson) Tun tun,
@Default(defaultDns) @JsonKey(fromJson: Dns.safeDnsFromJson) Dns dns,
@Default(defaultGeoXUrl)
@JsonKey(name: 'geox-url', fromJson: GeoXUrl.safeFormJson)
GeoXUrl geoXUrl,
@Default(GeodataLoader.memconservative)
@JsonKey(name: 'geodata-loader')
GeodataLoader geodataLoader,
@Default([]) @JsonKey(name: 'proxy-groups') List<ProxyGroup> proxyGroups,
@Default([]) List<String> rule,
@JsonKey(name: 'global-ua') String? globalUa,
@Default(ExternalControllerStatus.close)
@JsonKey(name: 'external-controller')
ExternalControllerStatus externalController,
@Default({}) HostsMap hosts,
}) = _ClashConfig;
factory ClashConfig.fromJson(Map<String, Object?> json) =>
_$ClashConfigFromJson(json);
factory ClashConfig.safeFormJson(Map<String, Object?>? json) {
if (json == null) {
return defaultClashConfig;
}
try {
return ClashConfig.fromJson(json);
} catch (_) {
return defaultClashConfig;
}
}
}