import 'dart:convert'; import 'package:kaer_with_panels/app/utils/kr_log_util.dart'; class KRNodeList { final List 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 json) { try { // 新的 API 返回格式: {"list": [{"id": 24, "is_try_out": true, "nodes": [...]}]} final List? listData = json['list'] as List?; if (listData == null || listData.isEmpty) { KRLogUtil.kr_w('节点列表为空', tag: 'NodeList'); return const KRNodeList(list: []); } // 尝试找到与请求参数 id 匹配的订阅项 // 如果 json 中有 subscribeId,则查找匹配项;否则使用第一个 Map? subscribeData; final String? requestSubscribeId = json['subscribeId']?.toString(); if (requestSubscribeId != null && requestSubscribeId.isNotEmpty) { // 查找 id 匹配的订阅项 try { subscribeData = listData.firstWhere( (item) => (item as Map)['id']?.toString() == requestSubscribeId, orElse: () => listData[0] as Map ) as Map; KRLogUtil.kr_i('✅ 找到匹配的订阅项: id=$requestSubscribeId', tag: 'NodeList'); } catch (e) { KRLogUtil.kr_w('⚠️ 未找到匹配的订阅项,使用第一个', tag: 'NodeList'); subscribeData = listData[0] as Map; } } else { // 如果没有提供 subscribeId,使用第一个 subscribeData = listData[0] as Map; } final bool isTryOut = subscribeData['is_try_out'] as bool? ?? false; final List? nodesData = subscribeData['nodes'] as List?; KRLogUtil.kr_i('🔍 节点列表解析: subscribe_id=${subscribeData['id']}, is_try_out=$isTryOut, 节点数=${nodesData?.length ?? 0}', tag: 'NodeList'); KRLogUtil.kr_i('📊 所有订阅项数量: ${listData.length}', tag: 'NodeList'); return KRNodeList( list: nodesData?.map((e) => KrNodeListItem.fromJson(e as Map)).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 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 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; // 优先使用 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 _parseStringList(dynamic value) { if (value == null) return []; if (value is List) { return value.map((e) => e?.toString() ?? '').toList(); } return []; } }