hi-client/lib/app/model/response/kr_node_list.dart

220 lines
7.2 KiB
Dart
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:convert';
import 'package:kaer_with_panels/app/utils/kr_log_util.dart';
class KRNodeList {
final List<KrNodeListItem> list;
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 {
// 新的 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: 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');
return const KRNodeList(list: []);
}
}
}
class KrNodeListItem {
final int id;
String name;
final String uuid;
final String protocol;
final String relayMode;
final String relayNode;
final String serverAddr;
final int port; // 端口字段
final String method; // 加密方法用于Shadowsocks等
final String protocols; // 协议配置JSON字符串新API格式
final int speedLimit;
final List<String> tags;
final int traffic;
final double trafficRatio;
final int upload;
final String city;
final String config;
final String country;
final int createdAt;
final int download;
final String startTime;
final String expireTime;
final double latitude;
final double latitudeCountry;
final double longitude;
final double longitudeCountry;
KrNodeListItem({
required this.id,
required this.name,
required this.uuid,
required this.protocol,
this.relayMode = '',
this.relayNode = '',
required this.serverAddr,
this.port = 0, // 默认值
this.method = '', // 默认空字符串
this.protocols = '', // 默认空字符串
required this.speedLimit,
required this.tags,
required this.traffic,
required this.trafficRatio,
required this.upload,
required this.city,
required this.config,
required this.country,
this.createdAt = 0,
required this.download,
required this.startTime,
required this.expireTime,
required this.latitude,
required this.latitudeCountry,
required this.longitude,
required this.longitudeCountry,
});
factory KrNodeListItem.fromJson(Map<String, dynamic> json) {
try {
// 支持新旧两种API格式
// 最新格式: protocols 字段包含协议配置数组
// 新格式: address, port, method直接字段
// 旧格式: server_addr, config 中包含 port
final serverAddr = json['address']?.toString() ?? json['server_addr']?.toString() ?? '';
int port = _parseIntSafely(json['port']);
String method = json['method']?.toString() ?? ''; // 加密方法Shadowsocks等
final protocols = json['protocols']?.toString() ?? ''; // 协议配置JSON
// 🔧 如果有 protocols 字段,从中解析 port 和 cipher
if (protocols.isNotEmpty) {
try {
final protocolsList = jsonDecode(protocols) as List;
if (protocolsList.isNotEmpty) {
final firstProtocol = protocolsList[0] as Map<String, dynamic>;
// 优先使用 protocols 中的配置
if (firstProtocol['port'] != null) {
port = _parseIntSafely(firstProtocol['port']);
}
if (firstProtocol['cipher'] != null && firstProtocol['cipher'].toString().isNotEmpty) {
method = firstProtocol['cipher'].toString();
}
KRLogUtil.kr_i('从 protocols 解析: port=$port, cipher=$method', tag: 'NodeList');
}
} catch (e) {
KRLogUtil.kr_w('解析 protocols 字段失败: $e', tag: 'NodeList');
}
}
return KrNodeListItem(
id: _parseIntSafely(json['id']),
name: json['name']?.toString() ?? '',
uuid: json['uuid']?.toString() ?? '',
protocol: json['protocol']?.toString() ?? '',
relayMode: json['relay_mode']?.toString() ?? '',
relayNode: json['relay_node']?.toString() ?? '',
serverAddr: serverAddr,
port: port,
method: method,
protocols: protocols,
speedLimit: _parseIntSafely(json['speed_limit']),
tags: _parseStringList(json['tags']),
traffic: _parseIntSafely(json['traffic']),
trafficRatio: _parseDoubleSafely(json['traffic_ratio']),
upload: _parseIntSafely(json['upload']),
city: json['city']?.toString() ?? '',
config: json['config']?.toString() ?? '',
country: json['country']?.toString() ?? '',
createdAt: _parseIntSafely(json['created_at']),
download: _parseIntSafely(json['download']),
startTime: json['start_time']?.toString() ?? '',
expireTime: json['expire_time']?.toString() ?? '',
latitude: _parseDoubleSafely(json['latitude']),
latitudeCountry: _parseDoubleSafely(json['latitude_country']),
longitude: _parseDoubleSafely(json['longitude']),
longitudeCountry: _parseDoubleSafely(json['longitude_country']),
);
} catch (err) {
KRLogUtil.kr_e('KrNodeListItem解析错误: $err', tag: 'NodeList');
return KrNodeListItem(
id: 0,
name: '',
uuid: '',
protocol: '',
serverAddr: '',
port: 0,
method: '',
protocols: '',
speedLimit: 0,
tags: [],
traffic: 0,
trafficRatio: 0,
upload: 0,
city: '',
config: '',
country: '',
download: 0,
startTime: '',
expireTime: '',
latitude: 0.0,
latitudeCountry: 0.0,
longitude: 0.0,
longitudeCountry: 0.0,
);
}
}
// 添加安全解析工具方法
static int _parseIntSafely(dynamic value) {
if (value == null) return 0;
if (value is int) return value;
if (value is String) return int.tryParse(value) ?? 0;
return 0;
}
static double _parseDoubleSafely(dynamic value) {
if (value == null) return 0.0;
if (value is double) return value;
if (value is int) return value.toDouble();
if (value is String) return double.tryParse(value) ?? 0.0;
return 0.0;
}
static List<String> _parseStringList(dynamic value) {
if (value == null) return [];
if (value is List) {
return value.map((e) => e?.toString() ?? '').toList();
}
return [];
}
}