feat: 提交国家分组功能和debug功能
This commit is contained in:
@@ -34,6 +34,60 @@ class KROutboundItem {
|
||||
|
||||
/// 构造函数,接受 KrItem 对象并初始化 KROutboundItem
|
||||
KROutboundItem(this.nodeListItem) {
|
||||
_initFromNodeListItem();
|
||||
}
|
||||
|
||||
/// 静态工厂:构造虚拟 urltest 节点(用于 ${country}-auto)
|
||||
factory KROutboundItem.fromVirtual(String tag, String country, Map<String, dynamic> config) {
|
||||
// 构造一个虚拟 KrNodeListItem,仅填充必要字段
|
||||
final virtualNode = KrNodeListItem(
|
||||
id: 0,
|
||||
name: tag,
|
||||
protocol: 'urltest',
|
||||
serverAddr: '',
|
||||
port: 0,
|
||||
uuid: '',
|
||||
config: jsonEncode(config),
|
||||
city: '',
|
||||
country: country,
|
||||
tags: [],
|
||||
latitude: 0,
|
||||
longitude: 0,
|
||||
latitudeCountry: 0,
|
||||
longitudeCountry: 0,
|
||||
relayNode: '',
|
||||
relayMode: 'none',
|
||||
protocols: '',
|
||||
method: '',
|
||||
speedLimit: 0,
|
||||
traffic: 0,
|
||||
trafficRatio: 0,
|
||||
upload: 0,
|
||||
download: 0,
|
||||
startTime: '',
|
||||
expireTime: '',
|
||||
);
|
||||
return KROutboundItem._virtual(virtualNode);
|
||||
}
|
||||
|
||||
/// 私有构造:用于虚拟节点,避免重复解析
|
||||
KROutboundItem._virtual(this.nodeListItem) {
|
||||
// 直接填充虚拟节点所需字段
|
||||
id = nodeListItem.id.toString();
|
||||
protocol = nodeListItem.protocol;
|
||||
tag = nodeListItem.name;
|
||||
serverAddr = nodeListItem.serverAddr;
|
||||
city = nodeListItem.city;
|
||||
country = nodeListItem.country;
|
||||
latitude = nodeListItem.latitude;
|
||||
latitudeCountry = nodeListItem.latitudeCountry;
|
||||
longitude = nodeListItem.longitude;
|
||||
longitudeCountry = nodeListItem.longitudeCountry;
|
||||
config = jsonDecode(nodeListItem.config); // 已知 config 有效
|
||||
}
|
||||
|
||||
/// 初始化逻辑提取,供主构造调用
|
||||
void _initFromNodeListItem() {
|
||||
id = nodeListItem.id.toString();
|
||||
protocol = nodeListItem.protocol;
|
||||
latitude = nodeListItem.latitude;
|
||||
@@ -56,167 +110,165 @@ class KROutboundItem {
|
||||
return;
|
||||
}
|
||||
|
||||
// 兜底:尝试解析 config 字段(旧API格式)
|
||||
if (nodeListItem.config.isEmpty) {
|
||||
if (kDebugMode) {
|
||||
print('❌ 节点 ${nodeListItem.name} 缺少配置信息(无port或config)');
|
||||
}
|
||||
config = {};
|
||||
return;
|
||||
}
|
||||
|
||||
late Map<String, dynamic> json;
|
||||
try {
|
||||
json = jsonDecode(nodeListItem.config) as Map<String, dynamic>;
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('❌ 节点 ${nodeListItem.name} 的 config 解析失败: $e,尝试使用直接字段');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
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 字段解析(旧API格式)
|
||||
if (nodeListItem.config.isNotEmpty) {
|
||||
try {
|
||||
final json = jsonDecode(nodeListItem.config) as Map<String, dynamic>;
|
||||
if (kDebugMode) {
|
||||
print('📄 解析到 config JSON: $json');
|
||||
}
|
||||
|
||||
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"
|
||||
// 提取 transport 配置
|
||||
Map<String, dynamic>? transportConfig;
|
||||
if (json['transport'] != null && json['transport'] != 'tcp') {
|
||||
transportConfig = _buildTransport(json);
|
||||
if (kDebugMode) {
|
||||
print('✅ 找到 transport 配置: $transportConfig');
|
||||
}
|
||||
}
|
||||
|
||||
// 提取 security_config
|
||||
Map<String, dynamic>? securityConfig;
|
||||
if (json['security_config'] != null) {
|
||||
securityConfig = json['security_config'] as Map<String, dynamic>;
|
||||
if (kDebugMode) {
|
||||
print('✅ 找到 security_config: $securityConfig');
|
||||
}
|
||||
}
|
||||
|
||||
// 根据协议类型构建配置
|
||||
switch (nodeListItem.protocol) {
|
||||
case "shadowsocks":
|
||||
config = {
|
||||
"type": "shadowsocks",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": json["port"],
|
||||
"method": json["method"],
|
||||
"password": nodeListItem.uuid
|
||||
};
|
||||
break;
|
||||
case "vless":
|
||||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||||
.hasMatch(nodeListItem.serverAddr);
|
||||
|
||||
config = {
|
||||
"type": "vless",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": json["port"],
|
||||
"uuid": nodeListItem.uuid,
|
||||
if (transportConfig != null) "transport": transportConfig,
|
||||
if (json["security"] == "tls") "tls": {
|
||||
"enabled": true,
|
||||
if (isDomain) "server_name": securityConfig?["sni"] ?? nodeListItem.serverAddr,
|
||||
"insecure": securityConfig?["allow_insecure"] ?? true,
|
||||
"utls": {
|
||||
"enabled": true,
|
||||
"fingerprint": securityConfig?["fingerprint"] ?? "chrome"
|
||||
}
|
||||
}
|
||||
};
|
||||
break;
|
||||
case "vmess":
|
||||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||||
.hasMatch(nodeListItem.serverAddr);
|
||||
|
||||
config = {
|
||||
"type": "vmess",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": json["port"],
|
||||
"uuid": nodeListItem.uuid,
|
||||
"alter_id": 0,
|
||||
"security": "auto",
|
||||
if (transportConfig != null) "transport": transportConfig,
|
||||
if (json["security"] == "tls") "tls": {
|
||||
"enabled": true,
|
||||
if (isDomain) "server_name": securityConfig?["sni"] ?? nodeListItem.serverAddr,
|
||||
"insecure": securityConfig?["allow_insecure"] ?? true,
|
||||
"utls": {
|
||||
"enabled": true,
|
||||
"fingerprint": securityConfig?["fingerprint"] ?? "chrome"
|
||||
}
|
||||
}
|
||||
};
|
||||
break;
|
||||
case "trojan":
|
||||
final bool isDomain = !RegExp(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$')
|
||||
.hasMatch(nodeListItem.serverAddr);
|
||||
|
||||
config = {
|
||||
"type": "trojan",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": json["port"],
|
||||
"password": nodeListItem.uuid,
|
||||
if (transportConfig != null) "transport": transportConfig,
|
||||
"tls": {
|
||||
"enabled": json["security"] == "tls",
|
||||
if (isDomain) "server_name": securityConfig?["sni"] ?? nodeListItem.serverAddr,
|
||||
"insecure": securityConfig?["allow_insecure"] ?? true,
|
||||
"utls": {
|
||||
"enabled": true,
|
||||
"fingerprint": securityConfig?["fingerprint"] ?? "chrome"
|
||||
}
|
||||
}
|
||||
};
|
||||
break;
|
||||
case "hysteria":
|
||||
case "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;
|
||||
default:
|
||||
if (kDebugMode) {
|
||||
print('⚠️ 不支持的协议类型: ${nodeListItem.protocol}');
|
||||
}
|
||||
}
|
||||
};
|
||||
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 = {};
|
||||
}
|
||||
|
||||
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"}
|
||||
// 检查 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
|
||||
}
|
||||
};
|
||||
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
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print('⚠️ 解析 config 字段失败: $e');
|
||||
}
|
||||
config = {};
|
||||
}
|
||||
}
|
||||
// 解析配置
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
// 现在它可以正确地访问已保存的 nodeListItem 成员
|
||||
return 'KROutboundItem(name: ${nodeListItem.name}, protocol: ${nodeListItem.protocol}, server: ${nodeListItem.serverAddr}, port: ${nodeListItem.port})';
|
||||
}
|
||||
|
||||
/// 构建传输配置
|
||||
Map<String, dynamic> _buildTransport(Map<String, dynamic> json) {
|
||||
final transportType = json["transport"] as String?;
|
||||
@@ -252,27 +304,6 @@ class KROutboundItem {
|
||||
if (kDebugMode) {
|
||||
print('🔧 开始构建节点配置 - 协议: ${nodeListItem.protocol}, 名称: ${nodeListItem.name}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print('📋 节点详细信息:');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - serverAddr: ${nodeListItem.serverAddr}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - port: ${nodeListItem.port}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - uuid: ${nodeListItem.uuid}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - method: ${nodeListItem.method}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - config: ${nodeListItem.config}');
|
||||
}
|
||||
if (kDebugMode) {
|
||||
print(' - protocols: ${nodeListItem.protocols}');
|
||||
}
|
||||
|
||||
// 🔧 尝试从 config 字段解析 transport 配置
|
||||
Map<String, dynamic>? transportConfig;
|
||||
|
||||
@@ -88,6 +88,9 @@ class KrOutboundsList {
|
||||
}
|
||||
}
|
||||
|
||||
// 生成国家自动选择虚拟节点
|
||||
_generateCountryAutoNodes(countryGroups);
|
||||
|
||||
// 将标签分组转换为 KRGroupOutboundList 并添加到 groupOutboundList
|
||||
for (var tag in tagGroups.keys) {
|
||||
final item = KRGroupOutboundList(
|
||||
@@ -108,7 +111,40 @@ class KrOutboundsList {
|
||||
country: country,
|
||||
outboundList: countryGroups[country]!)); // 添加国家分组到列表
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成国家自动选择虚拟节点
|
||||
void _generateCountryAutoNodes(Map<String, List<KROutboundItem>> countryGroups) {
|
||||
for (var entry in countryGroups.entries) {
|
||||
final country = entry.key;
|
||||
final nodes = entry.value;
|
||||
final autoTag = '${country}-auto';
|
||||
|
||||
if (kDebugMode) {
|
||||
print('🤖 生成国家自动选择节点: $autoTag, 包含 ${nodes.length} 个节点');
|
||||
}
|
||||
|
||||
// 构建 urltest 配置
|
||||
final urltestConfig = {
|
||||
'type': 'urltest',
|
||||
'tag': autoTag,
|
||||
'outbounds': nodes.map((node) => node.tag).toList(),
|
||||
'url': 'https://www.google.com/generate_204',
|
||||
'interval': '10m',
|
||||
'tolerance': 50,
|
||||
};
|
||||
|
||||
// 创建虚拟节点
|
||||
final virtualNode = KROutboundItem.fromVirtual(autoTag, country, urltestConfig);
|
||||
|
||||
// 添加到各个列表
|
||||
allList.add(virtualNode);
|
||||
keyList[autoTag] = virtualNode;
|
||||
configJsonList.add(urltestConfig);
|
||||
|
||||
if (kDebugMode) {
|
||||
print('✅ 生成虚拟节点: $autoTag, 配置: ${urltestConfig.toString()}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user