完善游客模式登录 并且新增设备管理
This commit is contained in:
@@ -45,7 +45,24 @@ class KROutboundItem {
|
||||
city = nodeListItem.city; // 设置城市
|
||||
country = nodeListItem.country; // 设置国家
|
||||
|
||||
final json = jsonDecode(nodeListItem.config);
|
||||
// 安全解析 config 字段
|
||||
// 新API格式:config为空,直接使用节点字段构建配置
|
||||
// 旧API格式:config包含JSON配置
|
||||
if (nodeListItem.config.isEmpty) {
|
||||
print('ℹ️ 节点 ${nodeListItem.name} 使用直接字段构建配置');
|
||||
_buildConfigFromFields(nodeListItem);
|
||||
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 =
|
||||
@@ -208,4 +225,95 @@ class KROutboundItem {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
/// 直接从节点字段构建配置(新API格式)
|
||||
void _buildConfigFromFields(KrNodeListItem nodeListItem) {
|
||||
switch (nodeListItem.protocol) {
|
||||
case "shadowsocks":
|
||||
config = {
|
||||
"type": "shadowsocks",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": nodeListItem.port,
|
||||
"method": "chacha20-ietf-poly1305", // 默认加密方法
|
||||
"password": nodeListItem.uuid
|
||||
};
|
||||
print('✅ Shadowsocks 节点配置构建成功: ${nodeListItem.name}');
|
||||
break;
|
||||
case "vless":
|
||||
config = {
|
||||
"type": "vless",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": nodeListItem.port,
|
||||
"uuid": nodeListItem.uuid,
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": nodeListItem.serverAddr,
|
||||
"insecure": false,
|
||||
"utls": {
|
||||
"enabled": true,
|
||||
"fingerprint": "chrome"
|
||||
}
|
||||
}
|
||||
};
|
||||
print('✅ VLESS 节点配置构建成功: ${nodeListItem.name}');
|
||||
break;
|
||||
case "vmess":
|
||||
config = {
|
||||
"type": "vmess",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": nodeListItem.port,
|
||||
"uuid": nodeListItem.uuid,
|
||||
"alter_id": 0,
|
||||
"security": "auto",
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": nodeListItem.serverAddr,
|
||||
"insecure": false,
|
||||
"utls": {"enabled": true, "fingerprint": "chrome"}
|
||||
}
|
||||
};
|
||||
print('✅ VMess 节点配置构建成功: ${nodeListItem.name}');
|
||||
break;
|
||||
case "trojan":
|
||||
config = {
|
||||
"type": "trojan",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": nodeListItem.port,
|
||||
"password": nodeListItem.uuid,
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": nodeListItem.serverAddr,
|
||||
"insecure": false,
|
||||
"utls": {"enabled": true, "fingerprint": "chrome"}
|
||||
}
|
||||
};
|
||||
print('✅ Trojan 节点配置构建成功: ${nodeListItem.name}');
|
||||
break;
|
||||
case "hysteria2":
|
||||
config = {
|
||||
"type": "hysteria2",
|
||||
"tag": nodeListItem.name,
|
||||
"server": nodeListItem.serverAddr,
|
||||
"server_port": nodeListItem.port,
|
||||
"password": nodeListItem.uuid,
|
||||
"up_mbps": 100,
|
||||
"down_mbps": 100,
|
||||
"tls": {
|
||||
"enabled": true,
|
||||
"server_name": nodeListItem.serverAddr,
|
||||
"insecure": false,
|
||||
"alpn": ["h3"]
|
||||
}
|
||||
};
|
||||
print('✅ Hysteria2 节点配置构建成功: ${nodeListItem.name}');
|
||||
break;
|
||||
default:
|
||||
print('⚠️ 不支持的协议类型: ${nodeListItem.protocol}');
|
||||
config = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +45,15 @@ class KrOutboundsList {
|
||||
}
|
||||
|
||||
final KROutboundItem item = KROutboundItem(element);
|
||||
|
||||
// 检查节点配置是否有效(必须包含 type 字段)
|
||||
if (item.config.isEmpty || !item.config.containsKey('type')) {
|
||||
print('⚠️ 跳过无效节点: ${element.name},配置为空或缺少 type 字段');
|
||||
continue; // 跳过无效节点
|
||||
}
|
||||
|
||||
allList.add(item);
|
||||
|
||||
|
||||
// 根据标签分组出站项
|
||||
for (var tag in element.tags) {
|
||||
tagGroups.putIfAbsent(tag, () => []);
|
||||
|
||||
@@ -5,23 +5,39 @@ class KRNodeList {
|
||||
final String subscribeId;
|
||||
final String startTime;
|
||||
final String expireTime;
|
||||
final bool isTryOut; // 是否是试用订阅
|
||||
|
||||
const KRNodeList({
|
||||
required this.list,
|
||||
this.subscribeId = "0",
|
||||
this.startTime = "",
|
||||
this.expireTime = "",
|
||||
this.isTryOut = false,
|
||||
});
|
||||
|
||||
factory KRNodeList.fromJson(Map<String, dynamic> json) {
|
||||
|
||||
try {
|
||||
final List<dynamic>? jsonList= json['list'] as List<dynamic>?;
|
||||
// 新的 API 返回格式: {"list": [{"id": 24, "is_try_out": true, "nodes": [...]}]}
|
||||
final List<dynamic>? listData = json['list'] as List<dynamic>?;
|
||||
|
||||
if (listData == null || listData.isEmpty) {
|
||||
KRLogUtil.kr_w('节点列表为空', tag: 'NodeList');
|
||||
return const KRNodeList(list: []);
|
||||
}
|
||||
|
||||
// 获取第一个订阅对象
|
||||
final subscribeData = listData[0] as Map<String, dynamic>;
|
||||
final bool isTryOut = subscribeData['is_try_out'] as bool? ?? false;
|
||||
final List<dynamic>? nodesData = subscribeData['nodes'] as List<dynamic>?;
|
||||
|
||||
KRLogUtil.kr_i('节点列表解析: is_try_out=$isTryOut, 节点数=${nodesData?.length ?? 0}', tag: 'NodeList');
|
||||
|
||||
return KRNodeList(
|
||||
list: jsonList?.map((e) => KrNodeListItem.fromJson(e as Map<String, dynamic>)).toList() ?? [],
|
||||
subscribeId: json['id']?.toString() ?? "0",
|
||||
startTime: json['start_time']?.toString() ?? "",
|
||||
expireTime: json['expire_time']?.toString() ?? "",
|
||||
list: nodesData?.map((e) => KrNodeListItem.fromJson(e as Map<String, dynamic>)).toList() ?? [],
|
||||
subscribeId: subscribeData['id']?.toString() ?? "0",
|
||||
startTime: subscribeData['start_time']?.toString() ?? "",
|
||||
expireTime: subscribeData['expire_time']?.toString() ?? "",
|
||||
isTryOut: isTryOut,
|
||||
);
|
||||
} catch (err) {
|
||||
KRLogUtil.kr_e('KRNodeList解析错误: $err', tag: 'NodeList');
|
||||
@@ -38,6 +54,7 @@ class KrNodeListItem {
|
||||
final String relayMode;
|
||||
final String relayNode;
|
||||
final String serverAddr;
|
||||
final int port; // 新增:端口字段
|
||||
final int speedLimit;
|
||||
final List<String> tags;
|
||||
final int traffic;
|
||||
@@ -63,6 +80,7 @@ class KrNodeListItem {
|
||||
this.relayMode = '',
|
||||
this.relayNode = '',
|
||||
required this.serverAddr,
|
||||
this.port = 0, // 默认值
|
||||
required this.speedLimit,
|
||||
required this.tags,
|
||||
required this.traffic,
|
||||
@@ -83,6 +101,12 @@ class KrNodeListItem {
|
||||
|
||||
factory KrNodeListItem.fromJson(Map<String, dynamic> json) {
|
||||
try {
|
||||
// 支持新旧两种API格式
|
||||
// 新格式: address, port
|
||||
// 旧格式: server_addr, config 中包含 port
|
||||
final serverAddr = json['address']?.toString() ?? json['server_addr']?.toString() ?? '';
|
||||
final port = _parseIntSafely(json['port']);
|
||||
|
||||
return KrNodeListItem(
|
||||
id: _parseIntSafely(json['id']),
|
||||
name: json['name']?.toString() ?? '',
|
||||
@@ -90,7 +114,8 @@ class KrNodeListItem {
|
||||
protocol: json['protocol']?.toString() ?? '',
|
||||
relayMode: json['relay_mode']?.toString() ?? '',
|
||||
relayNode: json['relay_node']?.toString() ?? '',
|
||||
serverAddr: json['server_addr']?.toString() ?? '',
|
||||
serverAddr: serverAddr,
|
||||
port: port,
|
||||
speedLimit: _parseIntSafely(json['speed_limit']),
|
||||
tags: _parseStringList(json['tags']),
|
||||
traffic: _parseIntSafely(json['traffic']),
|
||||
@@ -116,6 +141,7 @@ class KrNodeListItem {
|
||||
uuid: '',
|
||||
protocol: '',
|
||||
serverAddr: '',
|
||||
port: 0,
|
||||
speedLimit: 0,
|
||||
tags: [],
|
||||
traffic: 0,
|
||||
|
||||
@@ -10,6 +10,7 @@ class KRUserAvailableSubscribeItem {
|
||||
final String startTime;
|
||||
final String expireTime;
|
||||
final List<dynamic> list;
|
||||
final bool isTryOut; // 试用标志:true=试用,false=付费
|
||||
|
||||
const KRUserAvailableSubscribeItem({
|
||||
this.id = 0,
|
||||
@@ -21,19 +22,35 @@ class KRUserAvailableSubscribeItem {
|
||||
this.startTime = '',
|
||||
this.expireTime = '',
|
||||
this.list = const [],
|
||||
this.isTryOut = false,
|
||||
});
|
||||
|
||||
factory KRUserAvailableSubscribeItem.fromJson(Map<String, dynamic> json) {
|
||||
// 从 subscribe 对象中获取订阅信息
|
||||
final subscribe = json['subscribe'] as Map<String, dynamic>?;
|
||||
|
||||
// 时间字段可能是 int (毫秒时间戳) 或 String (ISO 8601)
|
||||
String convertTime(dynamic value) {
|
||||
if (value == null) return '';
|
||||
if (value is String) return value;
|
||||
if (value is int) {
|
||||
// 将毫秒时间戳转换为 ISO 8601 字符串
|
||||
return DateTime.fromMillisecondsSinceEpoch(value).toIso8601String();
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
return KRUserAvailableSubscribeItem(
|
||||
id: json['id'] as int? ?? 0,
|
||||
name: json['name'] as String? ?? '',
|
||||
deviceLimit: json['device_limit'] as int? ?? 0,
|
||||
name: subscribe?['name'] as String? ?? '',
|
||||
deviceLimit: subscribe?['device_limit'] as int? ?? 0,
|
||||
download: json['download'] as int? ?? 0,
|
||||
upload: json['upload'] as int? ?? 0,
|
||||
traffic: json['traffic'] as int? ?? 0,
|
||||
startTime: json['start_time'] as String? ?? '',
|
||||
expireTime: json['expire_time'] as String? ?? '',
|
||||
startTime: convertTime(json['start_time']),
|
||||
expireTime: convertTime(json['expire_time']),
|
||||
list: (json['list'] as List<dynamic>?) ?? const [],
|
||||
isTryOut: subscribe?['is_try_out'] as bool? ?? false,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -48,6 +65,7 @@ class KRUserAvailableSubscribeItem {
|
||||
'start_time': startTime,
|
||||
'expire_time': expireTime,
|
||||
'list': list,
|
||||
'is_try_out': isTryOut,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user