初始化提交

This commit is contained in:
2025-09-23 16:23:15 +08:00
commit 877b18a70f
590 changed files with 53617 additions and 0 deletions
+117
View File
@@ -0,0 +1,117 @@
import 'dart:io';
import 'package:freezed_annotation/freezed_annotation.dart';
// import 'package:kaer_with_panels/core/localization/translations.dart';
import 'package:kaer_with_panels/utils/platform_utils.dart';
@JsonEnum(valueField: 'key')
enum ServiceMode {
proxy("proxy"),
systemProxy("system-proxy"),
tun("vpn"),
tunService("vpn-service");
const ServiceMode(this.key);
final String key;
static ServiceMode get defaultMode =>
PlatformUtils.isDesktop ? systemProxy : tun;
/// supported service mode based on platform, use this instead of [values] in UI
static List<ServiceMode> get choices {
if (Platform.isWindows || Platform.isLinux) {
return values;
} else if (Platform.isMacOS) {
return [proxy, systemProxy, tun];
}
// mobile
return [proxy, tun];
}
bool get isExperimental => switch (this) {
tun => PlatformUtils.isDesktop,
tunService => PlatformUtils.isDesktop,
_ => false,
};
// String present(TranslationsEn t) => switch (this) {
// proxy => t.config.serviceModes.proxy,
// systemProxy => t.config.serviceModes.systemProxy,
// tun =>
// "${t.config.serviceModes.tun}${isExperimental ? " (${t.settings.experimental})" : ""}",
// tunService =>
// "${t.config.serviceModes.tunService}${isExperimental ? " (${t.settings.experimental})" : ""}",
// };
// String presentShort(TranslationsEn t) => switch (this) {
// proxy => t.config.shortServiceModes.proxy,
// systemProxy => t.config.shortServiceModes.systemProxy,
// tun => t.config.shortServiceModes.tun,
// tunService => t.config.shortServiceModes.tunService,
// };
}
@JsonEnum(valueField: 'key')
enum IPv6Mode {
disable("ipv4_only"),
enable("prefer_ipv4"),
prefer("prefer_ipv6"),
only("ipv6_only");
const IPv6Mode(this.key);
final String key;
// String present(TranslationsEn t) => switch (this) {
// disable => t.config.ipv6Modes.disable,
// enable => t.config.ipv6Modes.enable,
// prefer => t.config.ipv6Modes.prefer,
// only => t.config.ipv6Modes.only,
// };
}
@JsonEnum(valueField: 'key')
enum DomainStrategy {
auto(""),
preferIpv6("prefer_ipv6"),
preferIpv4("prefer_ipv4"),
ipv4Only("ipv4_only"),
ipv6Only("ipv6_only");
const DomainStrategy(this.key);
final String key;
String get displayName => switch (this) {
auto => "auto",
_ => key,
};
}
enum TunImplementation {
mixed,
system,
gvisor;
}
enum MuxProtocol {
h2mux,
smux,
yamux;
}
@JsonEnum(valueField: 'key')
enum WarpDetourMode {
proxyOverWarp("proxy_over_warp"),
warpOverProxy("warp_over_proxy");
const WarpDetourMode(this.key);
final String key;
// String present(TranslationsEn t) => switch (this) {
// proxyOverWarp => t.config.warpDetourModes.proxyOverWarp,
// warpOverProxy => t.config.warpDetourModes.warpOverProxy,
// };
}
+116
View File
@@ -0,0 +1,116 @@
import 'dart:convert';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:kaer_with_panels/core/model/optional_range.dart';
import 'package:kaer_with_panels/core/utils/json_converters.dart';
import 'package:kaer_with_panels/features/log/model/log_level.dart';
import 'package:kaer_with_panels/singbox/model/singbox_config_enum.dart';
import 'package:kaer_with_panels/singbox/model/singbox_rule.dart';
part 'singbox_config_option.freezed.dart';
part 'singbox_config_option.g.dart';
@freezed
class SingboxConfigOption with _$SingboxConfigOption {
const SingboxConfigOption._();
@JsonSerializable(fieldRename: FieldRename.kebab)
const factory SingboxConfigOption({
required String region,
required bool blockAds,
required bool useXrayCoreWhenPossible,
required bool executeConfigAsIs,
required LogLevel logLevel,
required bool resolveDestination,
required IPv6Mode ipv6Mode,
required String remoteDnsAddress,
required DomainStrategy remoteDnsDomainStrategy,
required String directDnsAddress,
required DomainStrategy directDnsDomainStrategy,
required int mixedPort,
required int tproxyPort,
required int localDnsPort,
required TunImplementation tunImplementation,
required int mtu,
required bool strictRoute,
required String connectionTestUrl,
@IntervalInSecondsConverter() required Duration urlTestInterval,
required bool enableClashApi,
required int clashApiPort,
required bool enableTun,
required bool enableTunService,
required bool setSystemProxy,
required bool bypassLan,
required bool allowConnectionFromLan,
required bool enableFakeDns,
required bool enableDnsRouting,
required bool independentDnsCache,
// required String geoipPath,
// required String geositePath,
required List<SingboxRule> rules,
required SingboxMuxOption mux,
required SingboxTlsTricks tlsTricks,
required SingboxWarpOption warp,
required SingboxWarpOption warp2,
}) = _SingboxConfigOption;
String format() {
const encoder = JsonEncoder.withIndent(' ');
return encoder.convert(toJson());
}
factory SingboxConfigOption.fromJson(Map<String, dynamic> json) =>
_$SingboxConfigOptionFromJson(json);
}
@freezed
class SingboxWarpOption with _$SingboxWarpOption {
@JsonSerializable(fieldRename: FieldRename.kebab)
const factory SingboxWarpOption({
required bool enable,
required WarpDetourMode mode,
required String wireguardConfig,
required String licenseKey,
required String accountId,
required String accessToken,
required String cleanIp,
required int cleanPort,
@OptionalRangeJsonConverter() required OptionalRange noise,
@OptionalRangeJsonConverter() required OptionalRange noiseSize,
@OptionalRangeJsonConverter() required OptionalRange noiseDelay,
@OptionalRangeJsonConverter() required String noiseMode,
}) = _SingboxWarpOption;
factory SingboxWarpOption.fromJson(Map<String, dynamic> json) =>
_$SingboxWarpOptionFromJson(json);
}
@freezed
class SingboxMuxOption with _$SingboxMuxOption {
@JsonSerializable(fieldRename: FieldRename.kebab)
const factory SingboxMuxOption({
required bool enable,
required bool padding,
required int maxStreams,
required MuxProtocol protocol,
}) = _SingboxMuxOption;
factory SingboxMuxOption.fromJson(Map<String, dynamic> json) =>
_$SingboxMuxOptionFromJson(json);
}
@freezed
class SingboxTlsTricks with _$SingboxTlsTricks {
@JsonSerializable(fieldRename: FieldRename.kebab)
const factory SingboxTlsTricks({
required bool enableFragment,
@OptionalRangeJsonConverter() required OptionalRange fragmentSize,
@OptionalRangeJsonConverter() required OptionalRange fragmentSleep,
required bool mixedSniCase,
required bool enablePadding,
@OptionalRangeJsonConverter() required OptionalRange paddingSize,
}) = _SingboxTlsTricks;
factory SingboxTlsTricks.fromJson(Map<String, dynamic> json) =>
_$SingboxTlsTricksFromJson(json);
}
+40
View File
@@ -0,0 +1,40 @@
import 'package:dartx/dartx.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:kaer_with_panels/singbox/model/singbox_proxy_type.dart';
part 'singbox_outbound.freezed.dart';
part 'singbox_outbound.g.dart';
@freezed
class SingboxOutboundGroup with _$SingboxOutboundGroup {
@JsonSerializable(fieldRename: FieldRename.kebab)
const factory SingboxOutboundGroup({
required String tag,
@JsonKey(fromJson: _typeFromJson) required ProxyType type,
required String selected,
@Default([]) List<SingboxOutboundGroupItem> items,
}) = _SingboxOutboundGroup;
factory SingboxOutboundGroup.fromJson(Map<String, dynamic> json) =>
_$SingboxOutboundGroupFromJson(json);
}
@freezed
class SingboxOutboundGroupItem with _$SingboxOutboundGroupItem {
const SingboxOutboundGroupItem._();
@JsonSerializable(fieldRename: FieldRename.kebab)
const factory SingboxOutboundGroupItem({
required String tag,
@JsonKey(fromJson: _typeFromJson) required ProxyType type,
required int urlTestDelay,
}) = _SingboxOutboundGroupItem;
factory SingboxOutboundGroupItem.fromJson(Map<String, dynamic> json) =>
_$SingboxOutboundGroupItemFromJson(json);
}
final Map<String, ProxyType> _keyMap =
Map.fromEntries(ProxyType.values.map((e) => MapEntry(e.key, e)));
ProxyType _typeFromJson(dynamic type) => ProxyType.fromJson(type);
+45
View File
@@ -0,0 +1,45 @@
enum ProxyType {
direct("Direct"),
block("Block"),
dns("DNS"),
socks("SOCKS"),
http("HTTP"),
shadowsocks("Shadowsocks"),
vmess("VMess"),
trojan("Trojan"),
naive("Naive"),
wireguard("WireGuard"),
hysteria("Hysteria"),
tor("Tor"),
ssh("SSH"),
shadowtls("ShadowTLS"),
shadowsocksr("ShadowsocksR"),
vless("VLESS"),
tuic("TUIC"),
hysteria2("Hysteria2"),
selector("Selector"),
urltest("URLTest"),
warp("Warp"),
xvless("xVLESS"),
xvmess("xVMess"),
xtrojan("xTrojan"),
xfreedom("xFragment"),
xshadowsocks("xShadowsocks"),
xsocks("xSocks"),
invalid("Invalid"),
unknown("Unknown");
const ProxyType(this.label);
final String label;
String get key => name;
static List<ProxyType> groupValues = [selector, urltest];
bool get isGroup => ProxyType.groupValues.contains(this);
static final Map<String, ProxyType> _keyMap = Map.fromEntries(ProxyType.values.map((e) => MapEntry(e.key, e)));
static ProxyType fromJson(dynamic type) => _keyMap[(type as String?)?.toLowerCase()] ?? ProxyType.unknown;
}
+35
View File
@@ -0,0 +1,35 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'singbox_rule.freezed.dart';
part 'singbox_rule.g.dart';
@freezed
class SingboxRule with _$SingboxRule {
const SingboxRule._();
@JsonSerializable(fieldRename: FieldRename.kebab)
const factory SingboxRule({
String? ruleSetUrl,
String? domains,
String? ip,
String? port,
String? protocol,
@Default(RuleNetwork.tcpAndUdp) RuleNetwork network,
@Default(RuleOutbound.proxy) RuleOutbound outbound,
}) = _SingboxRule;
factory SingboxRule.fromJson(Map<String, dynamic> json) => _$SingboxRuleFromJson(json);
}
enum RuleOutbound { proxy, bypass, block }
@JsonEnum(valueField: 'key')
enum RuleNetwork {
tcpAndUdp(""),
tcp("tcp"),
udp("udp");
const RuleNetwork(this.key);
final String? key;
}
+22
View File
@@ -0,0 +1,22 @@
import 'package:freezed_annotation/freezed_annotation.dart';
part 'singbox_stats.freezed.dart';
part 'singbox_stats.g.dart';
@freezed
class SingboxStats with _$SingboxStats {
const SingboxStats._();
@JsonSerializable(fieldRename: FieldRename.kebab)
const factory SingboxStats({
required int connectionsIn,
required int connectionsOut,
required int uplink,
required int downlink,
required int uplinkTotal,
required int downlinkTotal,
}) = _SingboxStats;
factory SingboxStats.fromJson(Map<String, dynamic> json) =>
_$SingboxStatsFromJson(json);
}
+50
View File
@@ -0,0 +1,50 @@
import 'package:dartx/dartx.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'singbox_status.freezed.dart';
@freezed
sealed class SingboxStatus with _$SingboxStatus {
const SingboxStatus._();
const factory SingboxStatus.stopped({
SingboxAlert? alert,
String? message,
}) = SingboxStopped;
const factory SingboxStatus.starting() = SingboxStarting;
const factory SingboxStatus.started() = SingboxStarted;
const factory SingboxStatus.stopping() = SingboxStopping;
factory SingboxStatus.fromEvent(dynamic event) {
switch (event) {
case {
"status": "Stopped",
"alert": final String? alertStr,
"message": final String? messageStr,
}:
final alert = SingboxAlert.values.firstOrNullWhere(
(e) => alertStr?.toLowerCase() == e.name.toLowerCase(),
);
return SingboxStatus.stopped(alert: alert, message: messageStr);
case {"status": "Stopped"}:
return const SingboxStatus.stopped();
case {"status": "Starting"}:
return const SingboxStarting();
case {"status": "Started"}:
return const SingboxStarted();
case {"status": "Stopping"}:
return const SingboxStopping();
default:
throw Exception("unexpected status [$event]");
}
}
}
enum SingboxAlert {
requestVPNPermission,
requestNotificationPermission,
emptyConfiguration,
startCommandServer,
createService,
startService;
}
+26
View File
@@ -0,0 +1,26 @@
import 'dart:convert';
typedef WarpResponse = ({
String log,
String accountId,
String accessToken,
String wireguardConfig,
});
WarpResponse warpFromJson(dynamic json) {
if (json
case {
"account-id": final String newAccountId,
"access-token": final String newAccessToken,
"log": final String log,
"config": final Map<String, dynamic> wireguardConfig,
}) {
return (
log: log,
accountId: newAccountId,
accessToken: newAccessToken,
wireguardConfig: jsonEncode(wireguardConfig),
);
}
throw Exception("invalid response");
}