omnAPP/lib/app/services/singbox_imp/kr_sing_box_imp.dart
Rust d87c58ac26
Some checks failed
Build Windows / build (push) Has been cancelled
修复安卓bug
2025-10-03 15:20:49 +08:00

656 lines
21 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 '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<String, dynamic> kr_configOption = {};
List<Map<String, dynamic>> 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<SingboxOutboundGroup> kr_activeGroups = <SingboxOutboundGroup>[].obs;
/// 所有的出站分组
RxList<SingboxOutboundGroup> kr_allGroups = <SingboxOutboundGroup>[].obs;
/// Stream 订阅管理器
final List<StreamSubscription<dynamic>> _kr_subscriptions = [];
/// 初始化
Future<void> 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<Map>("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<String, dynamic> _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<void> 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<Map<String, dynamic>> 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<void> 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<void> 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<StreamSubscription<dynamic>>.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<void> kr_restart() async {
KRLogUtil.kr_i("restart");
kr_singBox.restart(_cutPath, kr_configName, false).mapLeft((err) {
KRLogUtil.kr_e('重启失败: $err');
}).run();
}
//// 设置出站模式
Future<void> 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<void> 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<SingboxStatus> kr_watchStatus() {
return kr_singBox.watchStatus();
}
Stream<List<SingboxOutboundGroup>> 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<void> 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');
}
}
}