Support core status check and force restart Optimize proxies page and access page Update flutter and pub dependencies Update go version Optimize more details
483 lines
14 KiB
Dart
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;
|
|
}
|
|
}
|
|
}
|