572 lines
22 KiB
Dart
Executable File
572 lines
22 KiB
Dart
Executable File
import 'dart:convert';
|
||
import 'package:get/get.dart';
|
||
|
||
import '../response/kr_node_list.dart';
|
||
|
||
/// 表示出站项的模型类
|
||
class KROutboundItem {
|
||
int selected = 0; // 是否选中(0=未选中,1=选中)
|
||
String id = ""; // 标签
|
||
String tag = ""; // 标签
|
||
String serverAddr = ""; // 服务器地址
|
||
|
||
/// 初始化配置
|
||
Map<String, dynamic> config = {}; // 配置项
|
||
|
||
String city = ""; // 城市
|
||
String country = ""; // 国家
|
||
|
||
double latitude = 0.0; // 节点纬度
|
||
double latitudeCountry = 0.0; // 国家中心纬度
|
||
double longitude = 0.0; // 节点经度
|
||
double longitudeCountry = 0.0; // 国家中心经度
|
||
String protocol = "";
|
||
|
||
/// 延迟
|
||
RxInt urlTestDelay = 0.obs;
|
||
|
||
/// URL
|
||
String url = "";
|
||
@override
|
||
String toString() {
|
||
return 'KROutboundItem(tag: $tag, country: $country, delay: ${urlTestDelay.value}ms)';
|
||
}
|
||
/// 服务器类型
|
||
|
||
/// 构造函数,接受 KrNodeListItem 对象并初始化 KROutboundItem
|
||
KROutboundItem(KrNodeListItem nodeListItem) {
|
||
id = nodeListItem.id.toString();
|
||
protocol = nodeListItem.protocol;
|
||
latitude = nodeListItem.latitude;
|
||
latitudeCountry = nodeListItem.latitudeCountry;
|
||
longitude = nodeListItem.longitude;
|
||
longitudeCountry = nodeListItem.longitudeCountry;
|
||
|
||
tag = nodeListItem.name; // 设置标签
|
||
serverAddr = nodeListItem.serverAddr; // 设置服务器地址
|
||
city = nodeListItem.city; // 设置城市
|
||
country = nodeListItem.country; // 设置国家
|
||
|
||
// 🔧 优先使用直接字段构建配置(新API格式)
|
||
// 判断条件:如果有 port 和 serverAddr,说明是新格式
|
||
if (nodeListItem.port > 0 && nodeListItem.serverAddr.isNotEmpty) {
|
||
print('ℹ️ 节点 ${nodeListItem.name} 使用直接字段构建配置');
|
||
_buildConfigFromFields(nodeListItem);
|
||
return;
|
||
}
|
||
|
||
// 兜底:尝试解析 config 字段(旧API格式)
|
||
if (nodeListItem.config.isEmpty) {
|
||
print('❌ 节点 ${nodeListItem.name} 缺少配置信息(无port或config)');
|
||
config = {};
|
||
return;
|
||
}
|
||
|
||
late Map<String, dynamic> json;
|
||
try {
|
||
json = jsonDecode(nodeListItem.config) as Map<String, dynamic>;
|
||
} catch (e) {
|
||
print('❌ 节点 ${nodeListItem.name} 的 config 解析失败: $e,尝试使用直接字段');
|
||
print('📄 Config 内容: ${nodeListItem.config}');
|
||
_buildConfigFromFields(nodeListItem);
|
||
return;
|
||
}
|
||
switch (nodeListItem.protocol) {
|
||
case "vless":
|
||
final securityConfig =
|
||
json["security_config"] as Map<String, dynamic>? ?? {};
|
||
|
||
// 智能设置 server_name
|
||
String serverName = securityConfig["sni"] ?? "";
|
||
if (serverName.isEmpty) {
|
||
serverName = nodeListItem.serverAddr;
|
||
}
|
||
|
||
config = {
|
||
"type": "vless",
|
||
"tag": nodeListItem.name,
|
||
"server": nodeListItem.serverAddr,
|
||
"server_port": json["port"],
|
||
"uuid": nodeListItem.uuid,
|
||
if (json["flow"] != null && json["flow"] != "none")
|
||
"flow": json["flow"],
|
||
if (json["transport"] != null && json["transport"] != "tcp")
|
||
"transport": _buildTransport(json),
|
||
"tls": {
|
||
"enabled": json["security"] == "tls",
|
||
"server_name": serverName,
|
||
"insecure": securityConfig["allow_insecure"] ?? true,
|
||
"utls": {
|
||
"enabled": true,
|
||
"fingerprint": securityConfig["fingerprint"] ?? "chrome"
|
||
}
|
||
}
|
||
};
|
||
break;
|
||
case "vmess":
|
||
final securityConfig =
|
||
json["security_config"] as Map<String, dynamic>? ?? {};
|
||
|
||
// 智能设置 server_name
|
||
String serverName = securityConfig["sni"] ?? "";
|
||
if (serverName.isEmpty) {
|
||
serverName = nodeListItem.serverAddr;
|
||
}
|
||
|
||
config = {
|
||
"type": "vmess",
|
||
"tag": nodeListItem.name,
|
||
"server": nodeListItem.serverAddr,
|
||
"server_port": json["port"],
|
||
"uuid": nodeListItem.uuid,
|
||
"alter_id": 0,
|
||
"security": "auto",
|
||
if (json["transport"] != null && json["transport"] != "tcp")
|
||
"transport": _buildTransport(json),
|
||
"tls": {
|
||
"enabled": json["security"] == "tls",
|
||
"server_name": serverName,
|
||
"insecure": securityConfig["allow_insecure"] ?? true,
|
||
"utls": {"enabled": true, "fingerprint": "chrome"}
|
||
}
|
||
};
|
||
break;
|
||
case "shadowsocks":
|
||
config = {
|
||
"type": "shadowsocks",
|
||
"tag": nodeListItem.name,
|
||
"server": nodeListItem.serverAddr,
|
||
"server_port": json["port"],
|
||
"method": json["method"],
|
||
"password": nodeListItem.uuid
|
||
};
|
||
break;
|
||
case "hysteria":
|
||
case "hysteria2":
|
||
// 后端的 "hysteria" 实际上是 Hysteria2 协议
|
||
final securityConfig =
|
||
json["security_config"] as Map<String, dynamic>? ?? {};
|
||
config = {
|
||
"type": "hysteria2",
|
||
"tag": nodeListItem.name,
|
||
"server": nodeListItem.serverAddr,
|
||
"server_port": json["port"],
|
||
"password": nodeListItem.uuid,
|
||
"up_mbps": 100,
|
||
"down_mbps": 100,
|
||
"obfs": {
|
||
"type": "salamander",
|
||
"password": json["obfs_password"] ?? nodeListItem.uuid
|
||
},
|
||
"tls": {
|
||
"enabled": true,
|
||
"server_name": securityConfig["sni"] ?? "",
|
||
"insecure": securityConfig["allow_insecure"] ?? true
|
||
}
|
||
};
|
||
break;
|
||
case "trojan":
|
||
final securityConfig =
|
||
json["security_config"] as Map<String, dynamic>? ?? {};
|
||
|
||
// 智能设置 server_name
|
||
String serverName = securityConfig["sni"] ?? "";
|
||
if (serverName.isEmpty) {
|
||
// 如果没有配置 SNI,使用服务器地址
|
||
serverName = nodeListItem.serverAddr;
|
||
}
|
||
|
||
config = {
|
||
"type": "trojan",
|
||
"tag": nodeListItem.name,
|
||
"server": nodeListItem.serverAddr,
|
||
"server_port": json["port"],
|
||
"password": nodeListItem.uuid,
|
||
"tls": {
|
||
"enabled": json["security"] == "tls",
|
||
"server_name": serverName,
|
||
"insecure": securityConfig["allow_insecure"] ?? true,
|
||
"utls": {"enabled": true, "fingerprint": "chrome"}
|
||
}
|
||
};
|
||
break;
|
||
}
|
||
|
||
// 检查 relayNode 是否为 JSON 字符串并解析
|
||
if (nodeListItem.relayNode.isNotEmpty && nodeListItem.relayMode != "none") {
|
||
final relayNodeJson = jsonDecode(nodeListItem.relayNode);
|
||
if (relayNodeJson is List && nodeListItem.relayMode != "none") {
|
||
// 随机选择一个元素
|
||
final randomNode = (relayNodeJson..shuffle()).first;
|
||
config["server"] = randomNode["host"]; // 提取 host
|
||
config["server_port"] = randomNode["port"]; // 提取 port
|
||
}
|
||
}
|
||
// 解析配置
|
||
}
|
||
|
||
/// 构建传输配置
|
||
Map<String, dynamic> _buildTransport(Map<String, dynamic> json) {
|
||
final transportType = json["transport"] as String?;
|
||
final transportConfig =
|
||
json["transport_config"] as Map<String, dynamic>? ?? {};
|
||
|
||
switch (transportType) {
|
||
case "ws":
|
||
return {
|
||
"type": "ws",
|
||
"path": transportConfig["path"] ?? "/",
|
||
if (transportConfig["host"] != null)
|
||
"headers": {"Host": transportConfig["host"]}
|
||
};
|
||
case "grpc":
|
||
return {
|
||
"type": "grpc",
|
||
"service_name": transportConfig["service_name"] ?? ""
|
||
};
|
||
case "http":
|
||
return {
|
||
"type": "http",
|
||
"host": [transportConfig["host"] ?? ""],
|
||
"path": transportConfig["path"] ?? "/"
|
||
};
|
||
default:
|
||
return {};
|
||
}
|
||
}
|
||
|
||
/// 直接从节点字段构建配置(新API格式)
|
||
void _buildConfigFromFields(KrNodeListItem nodeListItem) {
|
||
print('🔧 开始构建节点配置 - 协议: ${nodeListItem.protocol}, 名称: ${nodeListItem.name}');
|
||
print('📋 节点详细信息:');
|
||
print(' - serverAddr: ${nodeListItem.serverAddr}');
|
||
print(' - port: ${nodeListItem.port}');
|
||
print(' - uuid: ${nodeListItem.uuid}');
|
||
print(' - method: ${nodeListItem.method}');
|
||
print(' - config: ${nodeListItem.config}');
|
||
print(' - protocols: ${nodeListItem.protocols}');
|
||
|
||
// 🔧 尝试从 config 字段解析 transport 配置
|
||
Map<String, dynamic>? transportConfig;
|
||
Map<String, dynamic>? securityConfig;
|
||
|
||
// 🔧 关键修复:优先从 protocols 字段解析配置
|
||
print('🔍 检查 protocols 字段: isEmpty=${nodeListItem.protocols.isEmpty}, length=${nodeListItem.protocols.length}');
|
||
if (nodeListItem.protocols.isNotEmpty) {
|
||
print('✅ protocols 字段不为空,开始解析...');
|
||
try {
|
||
final protocolsList = jsonDecode(nodeListItem.protocols) as List;
|
||
print('📄 解析到 protocols 数组,共 ${protocolsList.length} 个协议');
|
||
|
||
// 查找匹配当前协议类型的配置
|
||
Map<String, dynamic>? matchedProtocol;
|
||
for (var protocol in protocolsList) {
|
||
final protocolMap = protocol as Map<String, dynamic>;
|
||
final type = protocolMap['type']?.toString().toLowerCase() ?? '';
|
||
final enable = protocolMap['enable'] ?? true;
|
||
|
||
print(' 📋 协议: type=$type, enable=$enable');
|
||
|
||
// 匹配协议类型(注意 hysteria 和 hysteria2 都匹配 hysteria)
|
||
if (type == nodeListItem.protocol.toLowerCase() ||
|
||
(nodeListItem.protocol == 'hysteria' && type == 'hysteria2') ||
|
||
(nodeListItem.protocol == 'hysteria2' && type == 'hysteria')) {
|
||
matchedProtocol = protocolMap;
|
||
print(' ✅ 找到匹配的协议配置: $type');
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (matchedProtocol != null) {
|
||
// 提取 transport 配置
|
||
if (matchedProtocol['network'] != null || matchedProtocol['transport'] != null) {
|
||
final network = matchedProtocol['network'] ?? matchedProtocol['transport'];
|
||
print(' 📡 传输协议: $network');
|
||
|
||
if (network == 'ws' || network == 'websocket') {
|
||
transportConfig = {
|
||
'type': 'ws',
|
||
'path': matchedProtocol['ws_path'] ?? matchedProtocol['path'] ?? '/',
|
||
};
|
||
final host = matchedProtocol['ws_host'] ?? matchedProtocol['host'];
|
||
if (host != null && host.toString().isNotEmpty) {
|
||
transportConfig['headers'] = {'Host': host.toString()};
|
||
}
|
||
print(' ✅ WebSocket transport: $transportConfig');
|
||
} else if (network == 'grpc') {
|
||
transportConfig = {
|
||
'type': 'grpc',
|
||
'service_name': matchedProtocol['grpc_service_name'] ?? matchedProtocol['service_name'] ?? '',
|
||
};
|
||
print(' ✅ gRPC transport: $transportConfig');
|
||
} else if (network == 'http' || network == 'h2') {
|
||
transportConfig = {
|
||
'type': 'http',
|
||
'host': [matchedProtocol['http_host'] ?? matchedProtocol['host'] ?? ''],
|
||
'path': matchedProtocol['http_path'] ?? matchedProtocol['path'] ?? '/',
|
||
};
|
||
print(' ✅ HTTP transport: $transportConfig');
|
||
}
|
||
}
|
||
|
||
// 提取 security 配置
|
||
if (matchedProtocol['tls'] != null || matchedProtocol['security'] != null) {
|
||
// 🔧 关键修复:读取 security 字段判断是否启用 TLS
|
||
final security = matchedProtocol['security']?.toString().toLowerCase() ?? '';
|
||
final tlsEnabled = security == 'tls' || security == 'reality';
|
||
|
||
securityConfig = {
|
||
'tls_enabled': tlsEnabled,
|
||
'security_type': security, // 🔧 新增:记录安全类型(tls/reality)
|
||
'sni': matchedProtocol['sni'] ?? matchedProtocol['server_name'],
|
||
'allow_insecure': matchedProtocol['allow_insecure'] ?? matchedProtocol['insecure'] ?? true,
|
||
'fingerprint': matchedProtocol['fingerprint'] ?? 'chrome',
|
||
};
|
||
|
||
// 🔧 新增:如果是 Reality,提取 Reality 特定配置
|
||
if (security == 'reality') {
|
||
securityConfig['reality_enabled'] = true;
|
||
securityConfig['reality_public_key'] = matchedProtocol['reality_public_key'] ?? '';
|
||
securityConfig['reality_short_id'] = matchedProtocol['reality_short_id'] ?? '';
|
||
securityConfig['reality_server_name'] = matchedProtocol['reality_server_addr'] ?? matchedProtocol['reality_server_name'] ?? '';
|
||
|
||
print(' 🔒 Reality 配置:');
|
||
print(' - public_key: ${securityConfig['reality_public_key']}');
|
||
print(' - short_id: ${securityConfig['reality_short_id']}');
|
||
print(' - server_name: ${securityConfig['reality_server_name']}');
|
||
}
|
||
|
||
print(' ✅ Security config: security=$security, tls_enabled=$tlsEnabled, config=$securityConfig');
|
||
}
|
||
|
||
// 🔧 新增:提取 flow 字段(VLESS xtls-rprx-vision 等需要)
|
||
if (matchedProtocol['flow'] != null && matchedProtocol['flow'].toString().isNotEmpty) {
|
||
securityConfig ??= {};
|
||
securityConfig['flow'] = matchedProtocol['flow'].toString();
|
||
print(' 📊 Flow: ${securityConfig['flow']}');
|
||
}
|
||
|
||
// 🔧 新增:提取 encryption 字段(VLESS 需要)
|
||
if (matchedProtocol['encryption'] != null) {
|
||
securityConfig ??= {};
|
||
securityConfig['encryption'] = matchedProtocol['encryption'].toString();
|
||
print(' 🔐 Encryption: ${securityConfig['encryption']}');
|
||
}
|
||
}
|
||
} catch (e, stackTrace) {
|
||
print('⚠️ 解析 protocols 字段失败: $e');
|
||
print('📍 堆栈跟踪: $stackTrace');
|
||
}
|
||
} else {
|
||
print('❌ protocols 字段为空,跳过解析');
|
||
}
|
||
|
||
// 兜底:尝试从 config 字段解析(旧API格式)
|
||
if (transportConfig == null && nodeListItem.config.isNotEmpty) {
|
||
try {
|
||
final configJson = jsonDecode(nodeListItem.config) as Map<String, dynamic>;
|
||
print('📄 解析到 config JSON: $configJson');
|
||
|
||
// 提取 transport 配置
|
||
if (configJson['transport'] != null && configJson['transport'] != 'tcp') {
|
||
transportConfig = _buildTransport(configJson);
|
||
print('✅ 从 config 找到 transport 配置: $transportConfig');
|
||
}
|
||
|
||
// 提取 security_config
|
||
if (configJson['security_config'] != null) {
|
||
securityConfig = configJson['security_config'] as Map<String, dynamic>;
|
||
print('✅ 从 config 找到 security_config: $securityConfig');
|
||
}
|
||
} catch (e) {
|
||
print('⚠️ 解析 config 字段失败: $e');
|
||
}
|
||
}
|
||
|
||
switch (nodeListItem.protocol) {
|
||
case "shadowsocks":
|
||
// 优先使用 protocols 解析出来的 cipher,其次是 method 字段,最后才是默认值
|
||
String finalMethod = nodeListItem.method.isNotEmpty
|
||
? nodeListItem.method
|
||
: "2022-blake3-aes-256-gcm";
|
||
|
||
config = {
|
||
"type": "shadowsocks",
|
||
"tag": nodeListItem.name,
|
||
"server": nodeListItem.serverAddr,
|
||
"server_port": nodeListItem.port,
|
||
"method": finalMethod,
|
||
"password": nodeListItem.uuid
|
||
};
|
||
print('✅ Shadowsocks 节点配置构建成功: ${nodeListItem.name}');
|
||
print('📄 使用加密方法: $finalMethod');
|
||
print('📄 完整配置: $config');
|
||
break;
|
||
case "vless":
|
||
// 判断是否为域名(非IP地址)
|
||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||
.hasMatch(nodeListItem.serverAddr);
|
||
|
||
// 🔧 检测是否是 Reality 协议
|
||
final bool isReality = securityConfig?['security_type'] == 'reality';
|
||
final bool vlessTlsEnabled = securityConfig?['tls_enabled'] ?? false;
|
||
|
||
// 🔧 确定 server_name:Reality 使用 reality_server_name,普通 TLS 使用 sni
|
||
String serverName = nodeListItem.serverAddr;
|
||
if (isReality && securityConfig?['reality_server_name'] != null && securityConfig!['reality_server_name'].toString().isNotEmpty) {
|
||
serverName = securityConfig['reality_server_name'].toString();
|
||
print('🔒 Reality server_name: $serverName');
|
||
} else if (securityConfig != null && securityConfig['sni'] != null && securityConfig['sni'].toString().isNotEmpty) {
|
||
serverName = securityConfig['sni'].toString();
|
||
}
|
||
|
||
print('🔐 VLESS 配置: TLS=$vlessTlsEnabled, Reality=$isReality, isDomain=$isDomain');
|
||
print('🔐 Server Name: $serverName');
|
||
if (isReality) {
|
||
print('🔒 Reality 详细配置:');
|
||
print(' - public_key: ${securityConfig?['reality_public_key']}');
|
||
print(' - short_id: ${securityConfig?['reality_short_id']}');
|
||
print(' - server_name: ${securityConfig?['reality_server_name']}');
|
||
print(' - flow: ${securityConfig?['flow']}');
|
||
}
|
||
|
||
// 🔧 检查是否有 flow(vision 等流控)
|
||
final bool hasFlow = securityConfig?['flow'] != null &&
|
||
securityConfig!['flow'].toString().isNotEmpty &&
|
||
securityConfig['flow'].toString() != 'none';
|
||
|
||
config = {
|
||
"type": "vless",
|
||
"tag": nodeListItem.name,
|
||
"server": nodeListItem.serverAddr,
|
||
"server_port": nodeListItem.port,
|
||
"uuid": nodeListItem.uuid,
|
||
// 🔧 新增:flow 字段(xtls-rprx-vision 等)
|
||
if (hasFlow)
|
||
"flow": securityConfig['flow'].toString(),
|
||
// 🔧 关键修复:packet_encoding 和 flow 互斥
|
||
// 只有在没有 flow 时才添加 packet_encoding
|
||
if (!hasFlow)
|
||
"packet_encoding": "",
|
||
if (transportConfig != null) "transport": transportConfig,
|
||
if (vlessTlsEnabled) "tls": {
|
||
"enabled": true,
|
||
// 🔧 修复:Reality 必须有 server_name,普通 TLS 在域名时需要
|
||
if (isReality || isDomain) "server_name": serverName,
|
||
"insecure": securityConfig?['allow_insecure'] ?? true,
|
||
// 🔧 新增:Reality 配置块
|
||
if (isReality) "reality": {
|
||
"enabled": true,
|
||
"public_key": securityConfig?['reality_public_key'] ?? '',
|
||
"short_id": securityConfig?['reality_short_id'] ?? '',
|
||
},
|
||
"utls": {
|
||
"enabled": true,
|
||
"fingerprint": securityConfig?['fingerprint'] ?? "chrome"
|
||
}
|
||
}
|
||
};
|
||
print('✅ VLESS 节点配置构建成功: ${nodeListItem.name}');
|
||
print('📄 完整配置: $config');
|
||
break;
|
||
case "vmess":
|
||
// 判断是否为域名(非IP地址)
|
||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||
.hasMatch(nodeListItem.serverAddr);
|
||
|
||
// 🔧 优先使用 security_config 中的 SNI
|
||
String serverName = nodeListItem.serverAddr;
|
||
if (securityConfig != null && securityConfig['sni'] != null && securityConfig['sni'].toString().isNotEmpty) {
|
||
serverName = securityConfig['sni'].toString();
|
||
}
|
||
|
||
// 🔧 关键修复:根据 security_config 判断是否启用 TLS
|
||
final bool tlsEnabled = securityConfig?['tls_enabled'] ?? false;
|
||
print('🔐 TLS 状态: enabled=$tlsEnabled');
|
||
|
||
config = {
|
||
"type": "vmess",
|
||
"tag": nodeListItem.name,
|
||
"server": nodeListItem.serverAddr,
|
||
"server_port": nodeListItem.port,
|
||
"uuid": nodeListItem.uuid,
|
||
"alter_id": 0,
|
||
"security": "auto",
|
||
if (transportConfig != null) "transport": transportConfig,
|
||
if (tlsEnabled) "tls": {
|
||
"enabled": true,
|
||
if (isDomain) "server_name": serverName,
|
||
"insecure": securityConfig?['allow_insecure'] ?? true,
|
||
"utls": {
|
||
"enabled": true,
|
||
"fingerprint": securityConfig?['fingerprint'] ?? "chrome"
|
||
}
|
||
}
|
||
};
|
||
print('✅ VMess 节点配置构建成功: ${nodeListItem.name}');
|
||
print('📄 完整配置: $config');
|
||
break;
|
||
case "trojan":
|
||
// 判断是否为域名(非IP地址)
|
||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||
.hasMatch(nodeListItem.serverAddr);
|
||
|
||
// 🔧 优先使用 security_config 中的 SNI
|
||
String serverName = nodeListItem.serverAddr;
|
||
if (securityConfig != null && securityConfig['sni'] != null && securityConfig['sni'].toString().isNotEmpty) {
|
||
serverName = securityConfig['sni'].toString();
|
||
}
|
||
|
||
config = {
|
||
"type": "trojan",
|
||
"tag": nodeListItem.name,
|
||
"server": nodeListItem.serverAddr,
|
||
"server_port": nodeListItem.port,
|
||
"password": nodeListItem.uuid,
|
||
if (transportConfig != null) "transport": transportConfig,
|
||
"tls": {
|
||
"enabled": true,
|
||
if (isDomain) "server_name": serverName,
|
||
"insecure": securityConfig?['allow_insecure'] ?? true,
|
||
"utls": {
|
||
"enabled": true,
|
||
"fingerprint": securityConfig?['fingerprint'] ?? "chrome"
|
||
}
|
||
}
|
||
};
|
||
print('✅ Trojan 节点配置构建成功: ${nodeListItem.name}');
|
||
print('📄 完整配置: $config');
|
||
break;
|
||
case "hysteria":
|
||
case "hysteria2":
|
||
// 后端的 "hysteria" 实际上是 Hysteria2 协议
|
||
print('🔍 构建 Hysteria2 节点: ${nodeListItem.name}');
|
||
print(' - serverAddr: ${nodeListItem.serverAddr}');
|
||
print(' - port: ${nodeListItem.port}');
|
||
print(' - uuid: ${nodeListItem.uuid}');
|
||
|
||
//判断是否为域名
|
||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||
.hasMatch(nodeListItem.serverAddr);
|
||
|
||
config = {
|
||
"type": "hysteria2",
|
||
"tag": nodeListItem.name,
|
||
"server": nodeListItem.serverAddr,
|
||
"server_port": nodeListItem.port,
|
||
"password": nodeListItem.uuid,
|
||
"tls": {
|
||
"enabled": true,
|
||
if (isDomain) "server_name": nodeListItem.serverAddr,
|
||
}
|
||
};
|
||
print('✅ Hysteria2 节点配置构建成功');
|
||
print('📄 完整配置: ${jsonEncode(config)}');
|
||
break;
|
||
default:
|
||
print('⚠️ 不支持的协议类型: ${nodeListItem.protocol}');
|
||
config = {};
|
||
}
|
||
}
|
||
}
|