import 'dart:convert'; import 'dart:io'; import 'dart:async'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:kaer_with_panels/singbox/service/singbox_service.dart'; import 'package:kaer_with_panels/singbox/service/singbox_service_provider.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as p; import '../../../core/model/directories.dart'; import '../../../singbox/model/singbox_config_option.dart'; import '../../../singbox/model/singbox_outbound.dart'; import '../../../singbox/model/singbox_stats.dart'; import '../../../singbox/model/singbox_status.dart'; import '../../utils/kr_country_util.dart'; import '../../utils/kr_log_util.dart'; enum KRConnectionType { global, rule, // direct, } class KRSingBoxImp { /// 私有构造函数 KRSingBoxImp._(); /// 单例实例 static final KRSingBoxImp _instance = KRSingBoxImp._(); /// 工厂构造函数 factory KRSingBoxImp() => _instance; /// 获取实例的静态方法 static KRSingBoxImp get instance => _instance; /// 配置文件目录 late Directories kr_configDics; /// 配置文件名称 String kr_configName = "BearVPN"; /// 通道方法 final _kr_methodChannel = const MethodChannel("com.baer.app/platform"); final _kr_container = ProviderContainer(); /// 核心服务 late SingboxService kr_singBox; /// more配置 Map kr_configOption = {}; List> kr_outbounds = []; /// 首次启动 RxBool kr_isFristStart = false.obs; /// 状态 final kr_status = SingboxStatus.stopped().obs; /// 拦截广告 final kr_blockAds = true.obs; /// 是否自动自动选择线路 final kr_isAutoOutbound = true.obs; /// 连接类型 final kr_connectionType = KRConnectionType.rule.obs; String _cutPath = ""; /// 端口 int kr_port = 51213; /// 统计 final kr_stats = SingboxStats( connectionsIn: 0, connectionsOut: 0, uplink: 0, downlink: 0, uplinkTotal: 0, downlinkTotal: 0, ).obs; /// 活动的出站分组 RxList kr_activeGroups = [].obs; /// 所有的出站分组 RxList kr_allGroups = [].obs; /// Stream 订阅管理器 final List> _kr_subscriptions = []; /// 初始化 Future init() async { try { KRLogUtil.kr_i('开始初始化 SingBox'); // 在应用启动时初始化 await KRCountryUtil.kr_init(); KRLogUtil.kr_i('国家工具初始化完成'); final oOption = SingboxConfigOption.fromJson(_getConfigOption()); KRLogUtil.kr_i('配置选项初始化完成'); KRLogUtil.kr_i('开始初始化 SingBox 服务'); kr_singBox = await _kr_container.read(singboxServiceProvider); await _kr_container.read(singboxServiceProvider).init(); KRLogUtil.kr_i('SingBox 服务初始化完成'); KRLogUtil.kr_i('开始初始化目录'); /// 初始化目录 if (Platform.isIOS) { final paths = await _kr_methodChannel.invokeMethod("get_paths"); KRLogUtil.kr_i('iOS 路径获取完成: $paths'); kr_configDics = ( baseDir: Directory(paths?["base"]! as String), workingDir: Directory(paths?["working"]! as String), tempDir: Directory(paths?["temp"]! as String), ); } else { final baseDir = await getApplicationSupportDirectory(); final workingDir = Platform.isAndroid ? await getExternalStorageDirectory() : baseDir; final tempDir = await getTemporaryDirectory(); kr_configDics = ( baseDir: baseDir, workingDir: workingDir!, tempDir: tempDir, ); KRLogUtil.kr_i('其他平台路径初始化完成'); } KRLogUtil.kr_i('开始创建目录'); if (!kr_configDics.baseDir.existsSync()) { await kr_configDics.baseDir.create(recursive: true); } if (!kr_configDics.workingDir.existsSync()) { await kr_configDics.workingDir.create(recursive: true); } if (!kr_configDics.tempDir.existsSync()) { await kr_configDics.tempDir.create(recursive: true); } if (!directory.existsSync()) { await directory.create(recursive: true); } KRLogUtil.kr_i('目录创建完成'); KRLogUtil.kr_i('开始设置 SingBox'); await kr_singBox.setup(kr_configDics, false).map((r) { KRLogUtil.kr_i('SingBox 设置成功'); }).mapLeft((err) { KRLogUtil.kr_e('SingBox 设置失败: $err'); throw err; }).run(); KRLogUtil.kr_i('开始更新 SingBox 选项'); KRLogUtil.kr_i('📋 SingBox 配置选项: ${oOption.toJson()}', tag: 'SingBox'); await kr_singBox.changeOptions(oOption) ..map((r) { KRLogUtil.kr_i('✅ SingBox 选项更新成功', tag: 'SingBox'); }).mapLeft((err) { KRLogUtil.kr_e('❌ SingBox 选项更新失败: $err', tag: 'SingBox'); throw err; }).run(); KRLogUtil.kr_i('开始监听状态'); // 初始订阅状态流 kr_singBox.watchStatus().listen((status) { KRLogUtil.kr_i('🔄 SingBox 状态变化: $status', tag: 'SingBox'); KRLogUtil.kr_i('📊 状态类型: ${status.runtimeType}', tag: 'SingBox'); // 确保状态更新 kr_status.value = status; switch (status) { case SingboxStopped(): KRLogUtil.kr_i('🔴 SingBox 已停止', tag: 'SingBox'); break; case SingboxStarting(): KRLogUtil.kr_i('🟡 SingBox 正在启动', tag: 'SingBox'); break; case SingboxStarted(): KRLogUtil.kr_i('🟢 SingBox 已启动', tag: 'SingBox'); kr_isFristStart.value = true; // 使用 GetX 的方式处理 Stream 订阅 _kr_subscribeToStats(); _kr_subscribeToGroups(); // 强制触发状态更新 kr_status.refresh(); break; case SingboxStopping(): KRLogUtil.kr_i('🟠 SingBox 正在停止', tag: 'SingBox'); break; } }); KRLogUtil.kr_i('SingBox 初始化完成'); } catch (e, stackTrace) { KRLogUtil.kr_e('SingBox 初始化失败: $e'); KRLogUtil.kr_e('错误堆栈: $stackTrace'); rethrow; } } Map _getConfigOption() { if (kr_configOption.isNotEmpty) { return kr_configOption; } final op = { "region": KRCountryUtil.kr_getCurrentCountryCode(), "block-ads": kr_blockAds.value, "use-xray-core-when-possible": false, "execute-config-as-is": false, "log-level": "warn", "resolve-destination": false, "ipv6-mode": "ipv4_only", // "remote-dns-address": "https://cloudflare-dns.com/dns-query", "remote-dns-address": "udp://1.1.1.1", "remote-dns-domain-strategy": "", "direct-dns-address": "223.5.5.5", "direct-dns-domain-strategy": "", "mixed-port": kr_port, "tproxy-port": kr_port, "local-dns-port": 36450, "tun-implementation": "gvisor", "mtu": 9000, "strict-route": true, // "connection-test-url": "http://www.cloudflare.com", "connection-test-url": "http://www.gstatic.com/generate_204", "url-test-interval": 30, "enable-clash-api": true, "clash-api-port": 36756, "enable-tun": Platform.isIOS || Platform.isAndroid, "enable-tun-service": false, "set-system-proxy": Platform.isWindows || Platform.isLinux || Platform.isMacOS, "bypass-lan": false, "allow-connection-from-lan": false, "enable-fake-dns": false, "enable-dns-routing": true, "independent-dns-cache": true, "rules": [], "mux": { "enable": false, "padding": false, "max-streams": 8, "protocol": "h2mux" }, "tls-tricks": { "enable-fragment": false, "fragment-size": "10-30", "fragment-sleep": "2-8", "mixed-sni-case": false, "enable-padding": false, "padding-size": "1-1500" }, "warp": { "enable": false, "mode": "proxy_over_warp", "wireguard-config": "", "license-key": "", "account-id": "", "access-token": "", "clean-ip": "auto", "clean-port": 0, "noise": "1-3", "noise-size": "10-30", "noise-delay": "10-30", "noise-mode": "m4" }, "warp2": { "enable": false, "mode": "proxy_over_warp", "wireguard-config": "", "license-key": "", "account-id": "", "access-token": "", "clean-ip": "auto", "clean-port": 0, "noise": "1-3", "noise-size": "10-30", "noise-delay": "10-30", "noise-mode": "m4" } }; kr_configOption = op; return op; } /// 订阅统计数据流 void _kr_subscribeToStats() { // 取消之前的统计订阅 for (var sub in _kr_subscriptions) { if (sub.hashCode.toString().contains('Stats')) { sub.cancel(); } } _kr_subscriptions .removeWhere((sub) => sub.hashCode.toString().contains('Stats')); _kr_subscriptions.add( kr_singBox.watchStats().listen( (stats) { kr_stats.value = stats; }, onError: (error) { KRLogUtil.kr_e('统计数据监听错误: $error'); }, cancelOnError: false, ), ); } /// 订阅分组数据流 void _kr_subscribeToGroups() { // 取消之前的分组订阅 for (var sub in _kr_subscriptions) { if (sub.hashCode.toString().contains('Groups')) { sub.cancel(); } } _kr_subscriptions .removeWhere((sub) => sub.hashCode.toString().contains('Groups')); _kr_subscriptions.add( kr_singBox.watchActiveGroups().listen( (groups) { KRLogUtil.kr_i('📡 收到活动组更新,数量: ${groups.length}', tag: 'SingBox'); kr_activeGroups.value = groups; // 详细打印每个组的信息 for (int i = 0; i < groups.length; i++) { final group = groups[i]; KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'SingBox'); for (int j = 0; j < group.items.length; j++) { final item = group.items[j]; KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox'); } } KRLogUtil.kr_i('✅ 活动组处理完成', tag: 'SingBox'); }, onError: (error) { KRLogUtil.kr_e('❌ 活动分组监听错误: $error', tag: 'SingBox'); }, cancelOnError: false, ), ); _kr_subscriptions.add( kr_singBox.watchGroups().listen( (groups) { kr_allGroups.value = groups; }, onError: (error) { KRLogUtil.kr_e('所有分组监听错误: $error'); }, cancelOnError: false, ), ); } /// 监听活动组的详细实现 // Future watchActiveGroups() async { // try { // print("开始监听活动组详情..."); // final status = await kr_singBox.status(); // print("服务状态: ${status.toJson()}"); // final outbounds = await kr_singBox.listOutbounds(); // print("出站列表: ${outbounds.toJson()}"); // for (var outbound in outbounds.outbounds) { // print("出站配置: ${outbound.toJson()}"); // // 检查出站是否活动 // final isActive = await kr_singBox.isOutboundActive(outbound.tag); // print("出站 ${outbound.tag} 活动状态: $isActive"); // } // } catch (e, stack) { // print("监听活动组详情时出错: $e"); // print("错误堆栈: $stack"); // } // } /// 保存配置文件 void kr_saveOutbounds(List> outbounds) async { KRLogUtil.kr_i('💾 开始保存配置文件...', tag: 'SingBox'); KRLogUtil.kr_i('📊 出站节点数量: ${outbounds.length}', tag: 'SingBox'); kr_outbounds = outbounds; final map = {}; map["outbounds"] = kr_outbounds; final file = _file(kr_configName); final temp = _tempFile(kr_configName); final mapStr = jsonEncode(map); KRLogUtil.kr_i('📄 配置文件内容长度: ${mapStr.length}', tag: 'SingBox'); KRLogUtil.kr_i('📄 配置文件前500字符: ${mapStr.substring(0, mapStr.length > 500 ? 500 : mapStr.length)}', tag: 'SingBox'); await file.writeAsString(mapStr); await temp.writeAsString(mapStr); _cutPath = file.path; KRLogUtil.kr_i('📁 配置文件路径: $_cutPath', tag: 'SingBox'); await kr_singBox .validateConfigByPath(file.path, temp.path, false) .mapLeft((err) { KRLogUtil.kr_e('❌ 保存配置文件失败: $err', tag: 'SingBox'); }).run(); KRLogUtil.kr_i('✅ 配置文件保存完成', tag: 'SingBox'); } Future kr_start() async { kr_status.value = SingboxStarting(); try { KRLogUtil.kr_i('🚀 开始启动 SingBox...', tag: 'SingBox'); KRLogUtil.kr_i('📁 配置文件路径: $_cutPath', tag: 'SingBox'); KRLogUtil.kr_i('📝 配置名称: $kr_configName', tag: 'SingBox'); // 检查配置文件是否存在 final configFile = File(_cutPath); if (await configFile.exists()) { final configContent = await configFile.readAsString(); KRLogUtil.kr_i('📄 配置文件内容长度: ${configContent.length}', tag: 'SingBox'); KRLogUtil.kr_i('📄 配置文件前500字符: ${configContent.substring(0, configContent.length > 500 ? 500 : configContent.length)}', tag: 'SingBox'); } else { KRLogUtil.kr_w('⚠️ 配置文件不存在: $_cutPath', tag: 'SingBox'); } await kr_singBox.start(_cutPath, kr_configName, false).map( (r) { KRLogUtil.kr_i('✅ SingBox 启动成功', tag: 'SingBox'); }, ).mapLeft((err) { KRLogUtil.kr_e('❌ SingBox 启动失败: $err', tag: 'SingBox'); // 确保状态重置为Stopped,触发UI更新 kr_status.value = SingboxStopped(); // 强制刷新状态以触发观察者 kr_status.refresh(); throw err; }).run(); } catch (e, stackTrace) { KRLogUtil.kr_e('💥 SingBox 启动异常: $e', tag: 'SingBox'); KRLogUtil.kr_e('📚 错误堆栈: $stackTrace', tag: 'SingBox'); // 确保状态重置为Stopped,触发UI更新 kr_status.value = SingboxStopped(); // 强制刷新状态以触发观察者 kr_status.refresh(); rethrow; } } /// 停止服务 Future kr_stop() async { try { // 不主动赋值 kr_status await Future.delayed(const Duration(milliseconds: 100)); await kr_singBox.stop().run(); await Future.delayed(const Duration(milliseconds: 1000)); // 取消订阅 final subscriptions = List>.from(_kr_subscriptions); _kr_subscriptions.clear(); for (var subscription in subscriptions) { try { await subscription.cancel(); } catch (e) { KRLogUtil.kr_e('取消订阅时出错: $e'); } } // 不主动赋值 kr_status } catch (e, stackTrace) { KRLogUtil.kr_e('停止服务时出错: $e'); KRLogUtil.kr_e('错误堆栈: $stackTrace'); // 兜底,防止状态卡死 kr_status.value = SingboxStopped(); rethrow; } } /// void kr_updateAdBlockEnabled(bool bl) async { final oOption = _getConfigOption(); oOption["block-ads"] = bl; final op = SingboxConfigOption.fromJson(oOption); await kr_singBox.changeOptions(op) ..map((r) {}).mapLeft((err) { KRLogUtil.kr_e('更新广告拦截失败: $err'); }).run(); if (kr_status.value == SingboxStarted()) { await kr_restart(); } kr_blockAds.value = bl; } Future kr_restart() async { KRLogUtil.kr_i("restart"); kr_singBox.restart(_cutPath, kr_configName, false).mapLeft((err) { KRLogUtil.kr_e('重启失败: $err'); }).run(); } //// 设置出站模式 Future kr_updateConnectionType(KRConnectionType newType) async { if (kr_connectionType.value == newType) { return; } kr_connectionType.value = newType; final oOption = _getConfigOption(); var mode = ""; switch (newType) { case KRConnectionType.global: mode = "other"; break; case KRConnectionType.rule: mode = KRCountryUtil.kr_getCurrentCountryCode(); break; // case KRConnectionType.direct: // mode = "direct"; // break; } oOption["region"] = mode; final op = SingboxConfigOption.fromJson(oOption); await kr_singBox.changeOptions(op) ..map((r) {}).mapLeft((err) { KRLogUtil.kr_e('更新连接类型失败: $err'); }).run(); if (kr_status.value == SingboxStarted()) { await kr_restart(); } } /// 更新国家设置 Future kr_updateCountry(KRCountry kr_country) async { // 如果国家相同,直接返回 if (kr_country.kr_code == KRCountryUtil.kr_getCurrentCountryCode()) { return; } try { // 更新工具类中的当前国家 await KRCountryUtil.kr_setCurrentCountry(kr_country); // 更新配置选项 final oOption = _getConfigOption(); oOption["region"] = kr_country.kr_code; final op = SingboxConfigOption.fromJson(oOption); await kr_singBox.changeOptions(op) ..map((r) {}).mapLeft((err) { KRLogUtil.kr_e('更新国家设置失败: $err'); }).run(); // 如果服务正在运行,重启服务 if (kr_status.value == SingboxStarted()) { await kr_restart(); } } catch (err) { KRLogUtil.kr_e('更新国家失败: $err'); rethrow; } } Stream kr_watchStatus() { return kr_singBox.watchStatus(); } Stream> kr_watchGroups() { return kr_singBox.watchGroups(); } void kr_selectOutbound(String tag) { KRLogUtil.kr_i('🎯 开始选择出站节点: $tag', tag: 'SingBox'); KRLogUtil.kr_i('📊 当前活动组数量: ${kr_activeGroups.length}', tag: 'SingBox'); // 打印所有活动组信息 for (int i = 0; i < kr_activeGroups.length; i++) { final group = kr_activeGroups[i]; KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'SingBox'); for (int j = 0; j < group.items.length; j++) { final item = group.items[j]; KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox'); } } kr_singBox.selectOutbound("select", tag).run(); } /// 配合文件地址 Directory get directory => Directory(p.join(kr_configDics.workingDir.path, "configs")); File _file(String fileName) { return File(p.join(directory.path, "$fileName.json")); } File _tempFile(String fileName) => _file("$fileName.tmp"); // File tempFile(String fileName) => file("$fileName.tmp"); Future kr_urlTest(String groupTag) async { KRLogUtil.kr_i('🧪 开始 URL 测试: $groupTag', tag: 'SingBox'); KRLogUtil.kr_i('📊 当前活动组数量: ${kr_activeGroups.length}', tag: 'SingBox'); // 打印所有活动组信息 for (int i = 0; i < kr_activeGroups.length; i++) { final group = kr_activeGroups[i]; KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'SingBox'); for (int j = 0; j < group.items.length; j++) { final item = group.items[j]; KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox'); } } try { KRLogUtil.kr_i('🚀 调用 SingBox URL 测试 API...', tag: 'SingBox'); final result = await kr_singBox.urlTest(groupTag).run(); KRLogUtil.kr_i('✅ URL 测试完成: $groupTag, 结果: $result', tag: 'SingBox'); // 等待一段时间让 SingBox 完成测试 await Future.delayed(const Duration(seconds: 2)); // 再次检查活动组状态 KRLogUtil.kr_i('🔄 测试后活动组状态检查:', tag: 'SingBox'); for (int i = 0; i < kr_activeGroups.length; i++) { final group = kr_activeGroups[i]; KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'SingBox'); for (int j = 0; j < group.items.length; j++) { final item = group.items[j]; KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox'); } } } catch (e) { KRLogUtil.kr_e('❌ URL 测试失败: $groupTag, 错误: $e', tag: 'SingBox'); KRLogUtil.kr_e('📚 错误详情: ${e.toString()}', tag: 'SingBox'); } } }